View Javadoc

1   /*
2    * Copyright 2008 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 1.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl1.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.student.lum.course.service.assembler;
17  
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.Date;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  import java.util.Set;
28  
29  import org.apache.log4j.Logger;
30  import org.kuali.student.common.util.UUIDHelper;
31  import org.kuali.student.core.assembly.BOAssembler;
32  import org.kuali.student.core.assembly.BaseDTOAssemblyNode;
33  import org.kuali.student.core.assembly.BaseDTOAssemblyNode.NodeOperation;
34  import org.kuali.student.core.assembly.data.AssemblyException;
35  import org.kuali.student.core.dto.RichTextInfo;
36  import org.kuali.student.core.exceptions.DoesNotExistException;
37  import org.kuali.student.core.exceptions.InvalidParameterException;
38  import org.kuali.student.core.exceptions.MissingParameterException;
39  import org.kuali.student.core.exceptions.OperationFailedException;
40  import org.kuali.student.lum.course.dto.CourseCrossListingInfo;
41  import org.kuali.student.lum.course.dto.CourseExpenditureInfo;
42  import org.kuali.student.lum.course.dto.CourseFeeInfo;
43  import org.kuali.student.lum.course.dto.CourseInfo;
44  import org.kuali.student.lum.course.dto.CourseJointInfo;
45  import org.kuali.student.lum.course.dto.CourseRevenueInfo;
46  import org.kuali.student.lum.course.dto.CourseVariationInfo;
47  import org.kuali.student.lum.course.dto.FormatInfo;
48  import org.kuali.student.lum.course.dto.LoDisplayInfo;
49  import org.kuali.student.lum.lo.dto.LoInfo;
50  import org.kuali.student.lum.lo.service.LearningObjectiveService;
51  import org.kuali.student.lum.lrc.dto.ResultComponentInfo;
52  import org.kuali.student.lum.lrc.service.LrcService;
53  import org.kuali.student.lum.lu.dto.AdminOrgInfo;
54  import org.kuali.student.lum.lu.dto.CluAccountingInfo;
55  import org.kuali.student.lum.lu.dto.CluCluRelationInfo;
56  import org.kuali.student.lum.lu.dto.CluFeeInfo;
57  import org.kuali.student.lum.lu.dto.CluFeeRecordInfo;
58  import org.kuali.student.lum.lu.dto.CluIdentifierInfo;
59  import org.kuali.student.lum.lu.dto.CluInfo;
60  import org.kuali.student.lum.lu.dto.CluLoRelationInfo;
61  import org.kuali.student.lum.lu.dto.CluResultInfo;
62  import org.kuali.student.lum.lu.dto.LuCodeInfo;
63  import org.kuali.student.lum.lu.dto.ResultOptionInfo;
64  import org.kuali.student.lum.lu.service.LuService;
65  import org.kuali.student.lum.service.assembler.CluAssemblerUtils;
66  import org.springframework.util.StringUtils;
67  /**
68   * Assembler for CourseInfo. Provides assemble and disassemble operation on
69   * CourseInfo from/to CluInfo and other base DTOs
70   * 
71   * @author Kuali Student Team
72   * 
73   */
74  public class CourseAssembler implements BOAssembler<CourseInfo, CluInfo> {
75  
76      final static Logger LOG = Logger.getLogger(CourseAssembler.class);
77  	private LuService luService;
78  	private FormatAssembler formatAssembler;
79  	private CourseJointAssembler courseJointAssembler;
80  	private LoAssembler loAssembler;
81  	private LearningObjectiveService loService;
82      private CluAssemblerUtils cluAssemblerUtils;
83      private LrcService lrcService;
84  	
85  	@Override
86  	public CourseInfo assemble(CluInfo clu, CourseInfo courseInfo,
87  			boolean shallowBuild) throws AssemblyException {
88  
89  		CourseInfo course = (null != courseInfo) ? courseInfo
90  				: new CourseInfo();
91  
92  		// Copy all the data from the clu to the course
93  		
94  		course.setAttributes(clu.getAttributes());
95  		course.setCampusLocations(clu.getCampusLocations());
96  		course.setCode(clu.getOfficialIdentifier().getCode());
97  		course.setCourseNumberSuffix(clu.getOfficialIdentifier()
98  				.getSuffixCode());
99  		course.setLevel(clu.getOfficialIdentifier().getLevel());
100 		course.setOutOfClassHours(clu.getIntensity());
101 		course.setInstructors(clu.getInstructors());
102 		course.setStartTerm(clu.getExpectedFirstAtp());
103 		course.setEndTerm(clu.getLastAtp());
104 		course.setCourseTitle(clu.getOfficialIdentifier().getLongName());
105 
106 		// CrossListings
107 		List<CourseCrossListingInfo> crossListings = assembleCrossListings(clu.getAlternateIdentifiers()); 
108 		course.setCrossListings(crossListings);
109 		
110 		//Variation
111 		List<CourseVariationInfo> variations = assembleVariations(clu.getAlternateIdentifiers()); 
112 		course.setVariations(variations);
113 		
114 //		course.setDepartment(clu.getPrimaryAdminOrg().getOrgId());
115 		if(course.getUnitsDeployment()==null){
116 			course.setUnitsDeployment(new ArrayList<String>());
117 		}
118 		if(course.getUnitsContentOwner()==null){
119 			course.setUnitsContentOwner(new ArrayList<String>());
120 		}
121 		List<String> courseAdminOrgs = new ArrayList<String>();
122 		List<String> courseSubjectOrgs = new ArrayList<String>();
123 		for(AdminOrgInfo adminOrg: clu.getAdminOrgs()){
124 			if(adminOrg.getType().equals(CourseAssemblerConstants.ADMIN_ORG)){
125 				courseAdminOrgs.add(adminOrg.getOrgId());
126 			}
127 			if(adminOrg.getType().equals(CourseAssemblerConstants.SUBJECT_ORG)){
128 				courseSubjectOrgs.add(adminOrg.getOrgId());
129 			}
130 		}
131 		course.setUnitsDeployment(courseAdminOrgs);
132 		course.setUnitsContentOwner(courseSubjectOrgs);
133 		course.setDescr(clu.getDescr());
134 		course.setDuration(clu.getStdDuration());
135 		course.setEffectiveDate(clu.getEffectiveDate());
136 		course.setExpirationDate(clu.getExpirationDate());
137 
138 		//Fees
139 		//Fee justification
140 		List<CourseFeeInfo> fees = new ArrayList<CourseFeeInfo>();
141 		List<CourseRevenueInfo> revenues = new ArrayList<CourseRevenueInfo>();
142 		if(clu.getFeeInfo() != null){
143 			course.setFeeJustification(clu.getFeeInfo().getDescr());
144 
145 			//Fees and revenues come from the same place but revenues have a special feeType
146 			for(CluFeeRecordInfo cluFeeRecord: clu.getFeeInfo().getCluFeeRecords()){
147 				String feeType = cluFeeRecord.getFeeType();
148 				if(CourseAssemblerConstants.COURSE_FINANCIALS_REVENUE_TYPE.equals(feeType)){
149 					CourseRevenueInfo courseRevenue = new CourseRevenueInfo();
150 					courseRevenue.setFeeType(feeType);
151 					courseRevenue.setAffiliatedOrgs(cluFeeRecord.getAffiliatedOrgs());
152 					courseRevenue.setAttributes(cluFeeRecord.getAttributes());
153 					courseRevenue.setId(cluFeeRecord.getId());
154 					courseRevenue.setMetaInfo(cluFeeRecord.getMetaInfo());
155 					revenues.add(courseRevenue);
156 				}else{
157 					CourseFeeInfo courseFee = new CourseFeeInfo();
158 					courseFee.setFeeType(feeType);
159 					courseFee.setRateType(cluFeeRecord.getRateType());
160 					courseFee.setDescr(cluFeeRecord.getDescr());
161 					courseFee.setMetaInfo(cluFeeRecord.getMetaInfo());
162 					courseFee.setId(cluFeeRecord.getId());
163 					courseFee.setFeeAmounts(cluFeeRecord.getFeeAmounts());
164 					courseFee.setAttributes(cluFeeRecord.getAttributes());
165 					fees.add(courseFee);
166 				}
167 			}
168 		}
169 		course.setFees(fees);
170 		course.setRevenues(revenues);
171 		//Expenditures are mapped from accounting info
172 		if(course.getExpenditure() == null || clu.getAccountingInfo() == null){
173 			course.setExpenditure(new CourseExpenditureInfo());
174 		}
175 		if(clu.getAccountingInfo() != null){
176 			course.getExpenditure().setAffiliatedOrgs(clu.getAccountingInfo().getAffiliatedOrgs());
177 		}
178 		
179 		course.setId(clu.getId());
180 		course.setType(clu.getType());
181 		course.setTermsOffered(clu.getOfferedAtpTypes());
182 		course.setPrimaryInstructor(clu.getPrimaryInstructor());
183 		course.setInstructors(clu.getInstructors());
184 		course.setState(clu.getState());
185 		course.setSubjectArea(clu.getOfficialIdentifier().getDivision());
186 		course.setTranscriptTitle(clu.getOfficialIdentifier().getShortName());
187 		course.setMetaInfo(clu.getMetaInfo());
188 		course.setVersionInfo(clu.getVersionInfo());
189 
190 		
191 		//Special topics code
192 		course.setSpecialTopicsCourse(false);
193 		for(LuCodeInfo luCode : clu.getLuCodes()){
194 			if(CourseAssemblerConstants.COURSE_CODE_SPECIAL_TOPICS.equals(luCode.getType())){
195 				course.setSpecialTopicsCourse(Boolean.parseBoolean(luCode.getValue()));
196 				break;
197 			}
198 		}
199 		//Pilot Course code
200 		course.setPilotCourse(false);
201 		for(LuCodeInfo luCode : clu.getLuCodes()){
202 			if(CourseAssemblerConstants.COURSE_CODE_PILOT_COURSE.equals(luCode.getType())){
203 				course.setPilotCourse(Boolean.parseBoolean(luCode.getValue()));
204 				break;
205 			}
206 		}
207 		
208 		// Don't make any changes to nested datastructures if this is
209 		if (!shallowBuild) {
210 			try {
211 				// Use the luService to find Joints, then convert and add to the
212 				// course
213 				List<CluCluRelationInfo> cluClus = luService
214 						.getCluCluRelationsByClu(clu.getId());
215 				
216 				for (CluCluRelationInfo cluRel : cluClus) {
217 					if (cluRel.getType().equals(
218 							CourseAssemblerConstants.JOINT_RELATION_TYPE)) {
219 						CourseJointInfo jointInfo = courseJointAssembler
220 								.assemble(cluRel, null, false);
221 						course.getJoints().add(jointInfo);
222 					}
223 				}
224 			} catch (DoesNotExistException e) {
225 			} catch (Exception e) {
226 				throw new AssemblyException("Error getting course joints", e);
227 			}
228 
229 			try {
230 				// Use the luService to find formats, then convert and add to
231 				// the course
232 				List<CluInfo> formats = luService.getRelatedClusByCluId(course
233 						.getId(),
234 						CourseAssemblerConstants.COURSE_FORMAT_RELATION_TYPE);
235 				
236 				for (CluInfo format : formats) {
237 					FormatInfo formatInfo = formatAssembler.assemble(format,
238 							null, false);
239 					course.getFormats().add(formatInfo);
240 				}
241 
242 			} catch (DoesNotExistException e) {
243 			} catch (Exception e) {
244 				throw new AssemblyException("Error getting related formats", e);
245 			}
246 
247 			try{
248 				//Set Credit and Grading options
249 				List<CluResultInfo> cluResults = luService.getCluResultByClu(course.getId());
250 
251 				List<ResultComponentInfo> creditOptions = assembleCreditOptions(cluResults);
252 				course.setCreditOptions(creditOptions);
253 				
254 				List<String> gradingOptions = assembleGradingOptions(cluResults);
255 				
256 				course.setGradingOptions(gradingOptions);
257 			} catch (DoesNotExistException e){
258 			} catch (Exception e) {
259 				throw new AssemblyException("Error getting course results", e);
260 			}
261 			
262 			//Learning Objectives
263             course.getCourseSpecificLOs().addAll(cluAssemblerUtils.assembleLos(course.getId(), shallowBuild));
264 			
265 		}
266 
267 		//Remove special cases for grading options
268 		course.getGradingOptions().remove(CourseAssemblerConstants.COURSE_RESULT_COMP_GRADE_AUDIT);
269 		
270 		return course;
271 	}
272 
273 	@Override
274 	public BaseDTOAssemblyNode<CourseInfo, CluInfo> disassemble(
275 			CourseInfo course, NodeOperation operation)
276 			throws AssemblyException {
277 
278 		if (course == null) {
279 			// FIXME Unsure now if this is an exception or just return null or
280 			// empty assemblyNode
281 		    LOG.error("Course to disassemble is null!");
282 			throw new AssemblyException("Course can not be null");
283 		}
284 
285 		BaseDTOAssemblyNode<CourseInfo, CluInfo> result = new BaseDTOAssemblyNode<CourseInfo, CluInfo>(
286 				this);
287 
288 		CluInfo clu;
289 		try {
290 			clu = (NodeOperation.UPDATE == operation) ? luService.getClu(course.getId()) : new CluInfo();
291         } catch (Exception e) {
292 			throw new AssemblyException("Error getting existing learning unit during course update", e);
293         } 
294 
295 		// Create the id if it's not there already(important for creating
296 		// relations)
297 		clu.setId(UUIDHelper.genStringUUID(course.getId()));
298 		if (null == course.getId()) {
299 			course.setId(clu.getId());
300 		}
301 		clu.setType(CourseAssemblerConstants.COURSE_TYPE);
302 		clu.setState(course.getState());
303 
304 		CluIdentifierInfo identifier = new CluIdentifierInfo();
305 		identifier.setType(CourseAssemblerConstants.COURSE_OFFICIAL_IDENT_TYPE);
306 		identifier.setState(course.getState());
307 		identifier.setLongName(course.getCourseTitle());
308 		identifier.setShortName(course.getTranscriptTitle());
309 		identifier.setSuffixCode(course.getCourseNumberSuffix());
310 		identifier.setDivision(course.getSubjectArea());
311 
312 		//Custom logic to set the code as the concatenation of division and course number suffix if code not provided
313 		if (StringUtils.hasText(course.getCode())){
314 			identifier.setCode(course.getCode());
315 		} else if(course.getCourseNumberSuffix()!=null&&course.getSubjectArea()!=null&&!course.getCourseNumberSuffix().isEmpty()&&!course.getSubjectArea().isEmpty()){
316 			identifier.setCode(course.getSubjectArea()+course.getCourseNumberSuffix());			
317 		}else{
318 			identifier.setCode(null);
319 		}
320 		
321 		//Custom logic to set the level, if level not provided
322 		if(StringUtils.hasText(course.getLevel())) {
323 		    identifier.setLevel(course.getLevel());
324 		} else if(course.getCourseNumberSuffix()!=null&&course.getCourseNumberSuffix().length()>=3){
325 			identifier.setLevel(course.getCourseNumberSuffix().substring(0, 1)+"00");
326 		}
327 		
328 		clu.setOfficialIdentifier(identifier);
329 
330 		clu.setAdminOrgs(new ArrayList<AdminOrgInfo>());
331 
332 		// Use the Course Variation assembler to disassemble the variations
333 		
334 		// copy all fields
335 		//Remove any existing variations or crosslistings
336 		for(Iterator<CluIdentifierInfo> iter = clu.getAlternateIdentifiers().iterator();iter.hasNext();){
337 			CluIdentifierInfo cluIdentifier = iter.next();
338 			if(CourseAssemblerConstants.COURSE_VARIATION_IDENT_TYPE.equals(cluIdentifier.getType()) ||
339 				CourseAssemblerConstants.COURSE_CROSSLISTING_IDENT_TYPE.equals(cluIdentifier.getType()) ){
340 				iter.remove();
341 			}
342 		}
343 		//Add in variations
344 		for(CourseVariationInfo variation:course.getVariations()){
345 			CluIdentifierInfo cluIdentifier = new CluIdentifierInfo();
346 			cluIdentifier.setId(variation.getId());
347 			cluIdentifier.setType(CourseAssemblerConstants.COURSE_VARIATION_IDENT_TYPE);
348 			cluIdentifier.setCode(identifier.getCode());
349 			cluIdentifier.setSuffixCode(course.getCourseNumberSuffix());
350 			cluIdentifier.setDivision(course.getSubjectArea());
351 			cluIdentifier.setVariation(variation.getVariationCode());
352 			cluIdentifier.setLongName(variation.getVariationTitle());
353 			cluIdentifier.setState(course.getState());
354 			clu.getAlternateIdentifiers().add(cluIdentifier);
355 		}
356 		//Add in crossListings
357 		for(CourseCrossListingInfo crossListing:course.getCrossListings()){
358 			CluIdentifierInfo cluIdentifier = new CluIdentifierInfo();
359 			cluIdentifier.setId(crossListing.getId());
360 			cluIdentifier.setType(CourseAssemblerConstants.COURSE_CROSSLISTING_IDENT_TYPE);
361 			cluIdentifier.setSuffixCode(crossListing.getCourseNumberSuffix());
362 			cluIdentifier.setDivision(crossListing.getSubjectArea());
363 			cluIdentifier.setState(course.getState());
364 			cluIdentifier.setOrgId(crossListing.getDepartment());
365 			clu.getAlternateIdentifiers().add(cluIdentifier);
366 		}
367 
368 		List<AdminOrgInfo> adminOrgInfos = new ArrayList<AdminOrgInfo>();
369 		for(String org:course.getUnitsDeployment()){
370 			AdminOrgInfo adminOrg = new AdminOrgInfo();
371 			adminOrg.setType(CourseAssemblerConstants.ADMIN_ORG);
372 			adminOrg.setOrgId(org);
373 			adminOrgInfos.add(adminOrg);
374 		}
375 		clu.getAdminOrgs().addAll(adminOrgInfos);
376 		
377 		List<AdminOrgInfo> subjectOrgs = new ArrayList<AdminOrgInfo>();
378 		for (String subOrg : course.getUnitsContentOwner()) {
379 			AdminOrgInfo subjectOrg = new AdminOrgInfo();
380 			subjectOrg.setType(CourseAssemblerConstants.SUBJECT_ORG);
381 			subjectOrg.setOrgId(subOrg);
382 			subjectOrgs.add(subjectOrg);
383 		}
384 		clu.getAdminOrgs().addAll(subjectOrgs);
385 
386 		
387 		clu.setAttributes(course.getAttributes());
388 		clu.setCampusLocations(course.getCampusLocations());
389 		clu.setDescr(course.getDescr());
390 		clu.setStdDuration(course.getDuration());
391 		clu.setEffectiveDate(course.getEffectiveDate());
392 		clu.setExpirationDate(course.getExpirationDate());
393 
394 		clu.setOfferedAtpTypes(course.getTermsOffered());
395 		clu.setPrimaryInstructor(course.getPrimaryInstructor());
396 		
397 		clu.setIntensity(course.getOutOfClassHours());
398 		clu.setInstructors(course.getInstructors());
399 		
400 		clu.setExpectedFirstAtp(course.getStartTerm());
401 		clu.setLastAtp(course.getEndTerm());
402 		
403 		clu.setMetaInfo(course.getMetaInfo());
404 		clu.setVersionInfo(course.getVersionInfo());
405 
406 		// Add the Clu to the result
407 		result.setNodeData(clu);
408 		result.setOperation(operation);
409 		result.setBusinessDTORef(course);
410 
411 		// Use the Format assembler to disassemble the formats and relations
412 		List<BaseDTOAssemblyNode<?, ?>> formatResults;
413         try {
414             formatResults = disassembleFormats(clu
415             		.getId(), course, operation);
416             result.getChildNodes().addAll(formatResults);
417             
418         } catch (DoesNotExistException e) {
419         } catch (Exception e) {
420             throw new AssemblyException("Error while disassembling format", e);
421         }
422 
423 		// Use the CourseJoint assembler to disassemble the CourseJoints and
424 		// relations
425 		List<BaseDTOAssemblyNode<?, ?>> courseJointResults = disassembleJoints(
426 				clu.getId(), course, operation);
427 		result.getChildNodes().addAll(courseJointResults);
428 
429 		//Disassemble the CluResults (grading and credit options)
430 		//Special code to take audit from attributes and put into options
431 		if(course.getAttributes().containsKey(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_AUDIT)&&"true".equals(course.getAttributes().get(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_AUDIT))){
432 			if(!course.getGradingOptions().contains(CourseAssemblerConstants.COURSE_RESULT_COMP_GRADE_AUDIT)){
433 				course.getGradingOptions().add(CourseAssemblerConstants.COURSE_RESULT_COMP_GRADE_AUDIT);
434 			}
435 		}
436 		
437 		List<CluResultInfo> cluResultList;
438 		try {
439 			cluResultList = luService.getCluResultByClu(clu.getId());
440 		} catch (DoesNotExistException e) {
441 			cluResultList = Collections.emptyList();
442 		} catch (Exception e) {
443 			throw new AssemblyException("Error getting cluResults", e);
444 		}
445 		
446 		List<BaseDTOAssemblyNode<?, ?>> creditOutcomes = disassembleCreditOutcomes(course, clu, cluResultList, operation);
447 		result.getChildNodes().addAll(creditOutcomes);
448 		
449 		BaseDTOAssemblyNode<?, ?> gradingOptions = disassembleGradingOptions(
450 				clu.getId(), course.getState(), course.getGradingOptions(), cluResultList, operation);
451 		result.getChildNodes().add(gradingOptions);
452 		
453 		//Use the LoAssembler to disassemble Los
454         try {
455     		List<BaseDTOAssemblyNode<?, ?>> loResults;
456     		loResults = disassembleLos(clu.getId(), course, operation);
457             result.getChildNodes().addAll(loResults);
458         } catch (Exception e) {
459             throw new AssemblyException("Error while disassembling los", e);
460         }
461 		
462 		//add the special topics code if it did not exist, or remove if it was not wanted
463 		boolean alreadyHadSpecialTopicsCode = false;
464 		for(Iterator<LuCodeInfo> luCodeIterator = clu.getLuCodes().iterator();luCodeIterator.hasNext();){
465 			LuCodeInfo luCode = luCodeIterator.next();
466 			if(CourseAssemblerConstants.COURSE_CODE_SPECIAL_TOPICS.equals(luCode.getType())){
467 				alreadyHadSpecialTopicsCode = true;
468 				if(!course.isSpecialTopicsCourse()){
469 					luCodeIterator.remove();
470 				}
471 				break;
472 			}
473 		}
474 		if(!alreadyHadSpecialTopicsCode && course.isSpecialTopicsCourse()){
475 			LuCodeInfo luCode = new LuCodeInfo();
476 			luCode.setType(CourseAssemblerConstants.COURSE_CODE_SPECIAL_TOPICS);
477 			luCode.setValue("true");
478 			clu.getLuCodes().add(luCode);
479 		}
480 		
481 		//add the special topics code if it did not exist, or remove if it was not wanted
482 		boolean alreadyHadPilotCourseCode = false;
483 		for(Iterator<LuCodeInfo> luCodeIterator = clu.getLuCodes().iterator();luCodeIterator.hasNext();){
484 			LuCodeInfo luCode = luCodeIterator.next();
485 			if(CourseAssemblerConstants.COURSE_CODE_PILOT_COURSE.equals(luCode.getType())){
486 				alreadyHadPilotCourseCode = true;
487 				if(!course.isPilotCourse()){
488 					luCodeIterator.remove();
489 				}
490 				break;
491 			}
492 		}
493 		if(!alreadyHadPilotCourseCode && course.isPilotCourse()){
494 			LuCodeInfo luCode = new LuCodeInfo();
495 			luCode.setType(CourseAssemblerConstants.COURSE_CODE_PILOT_COURSE);
496 			luCode.setValue("true");
497 			clu.getLuCodes().add(luCode);
498 		}
499 		
500 		//FEES
501 		if(clu.getFeeInfo() == null){
502 			clu.setFeeInfo(new CluFeeInfo());
503 		}
504 		clu.getFeeInfo().setDescr(course.getFeeJustification());
505 		clu.getFeeInfo().getCluFeeRecords().clear();
506 		for(CourseRevenueInfo courseRevenue:course.getRevenues()){
507 			CluFeeRecordInfo cluFeeRecord  = new CluFeeRecordInfo();
508 			cluFeeRecord.setFeeType(CourseAssemblerConstants.COURSE_FINANCIALS_REVENUE_TYPE);
509 			cluFeeRecord.setRateType(CourseAssemblerConstants.COURSE_FINANCIALS_REVENUE_TYPE);
510 			cluFeeRecord.setAttributes(courseRevenue.getAttributes());
511 			cluFeeRecord.setAffiliatedOrgs(courseRevenue.getAffiliatedOrgs());
512 			cluFeeRecord.setId(courseRevenue.getId());
513 			cluFeeRecord.setMetaInfo(courseRevenue.getMetaInfo());
514 			clu.getFeeInfo().getCluFeeRecords().add(cluFeeRecord);
515 		}
516 		for(CourseFeeInfo courseFee : course.getFees()){
517 			CluFeeRecordInfo cluFeeRecord  = new CluFeeRecordInfo();
518 			cluFeeRecord.setFeeType(courseFee.getFeeType());
519 			cluFeeRecord.setRateType(courseFee.getRateType());
520 			cluFeeRecord.setDescr(courseFee.getDescr());
521 			cluFeeRecord.setMetaInfo(courseFee.getMetaInfo());
522 			cluFeeRecord.setId(courseFee.getId());
523 			cluFeeRecord.setFeeAmounts(courseFee.getFeeAmounts());
524 			cluFeeRecord.setAttributes(courseFee.getAttributes());
525 			clu.getFeeInfo().getCluFeeRecords().add(cluFeeRecord);
526 		}
527 		if(clu.getAccountingInfo() == null || course.getExpenditure()== null){
528 			clu.setAccountingInfo( new CluAccountingInfo());
529 		}
530 		if(course.getExpenditure() != null){
531 			clu.getAccountingInfo().setAffiliatedOrgs(course.getExpenditure().getAffiliatedOrgs());
532 			clu.getAccountingInfo().setAttributes(course.getExpenditure().getAttributes());
533 		}
534 		
535 		return result;
536 	}
537 
538 	private List<BaseDTOAssemblyNode<?, ?>> disassembleCreditOutcomes(CourseInfo course, CluInfo clu, List<CluResultInfo> currentCluResults, NodeOperation operation) throws AssemblyException, NumberFormatException {
539 		
540 		List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
541 		
542 		String courseResultType = CourseAssemblerConstants.COURSE_RESULT_TYPE_CREDITS;
543 		
544 		//See if we need to create any new lrcs
545 		if(NodeOperation.DELETE!=operation){
546 			//Find all the existing LRCs for the following three types
547 			Set<String> rsltComps = new HashSet<String>();
548 			
549 			try{
550 				try {
551 					rsltComps.addAll(lrcService.getResultComponentIdsByResultComponentType(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_FIXED));
552 				} catch (DoesNotExistException e) {}
553 				try {
554 					rsltComps.addAll(lrcService.getResultComponentIdsByResultComponentType(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_MULTIPLE));
555 				} catch (DoesNotExistException e) {}
556 				try {
557 					rsltComps.addAll(lrcService.getResultComponentIdsByResultComponentType(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_VARIABLE));
558 				} catch (DoesNotExistException e) {}
559 
560 				//Create any LRCs that do not yet exist
561 				for(ResultComponentInfo creditOption:course.getCreditOptions()){
562 					String id = null;
563 					String type = null;
564 					List<String> resultValues = null;
565 					Map<String,String> attributes = null;
566 					//Depending on the type, set the id, type and result values differently
567 					if(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_FIXED.equals(creditOption.getType())){
568 						float fixedCreditValue = Float.parseFloat(creditOption.getAttributes().get(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_FIXED_CREDIT_VALUE));
569 						id = CourseAssemblerConstants.COURSE_RESULT_COMP_CREDIT_PREFIX + fixedCreditValue;
570 						type = CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_FIXED;
571 						resultValues = new ArrayList<String>();
572 						resultValues.add(String.valueOf(fixedCreditValue));
573 						attributes = new HashMap<String,String>();
574 						attributes.put(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_FIXED_CREDIT_VALUE, String.valueOf(fixedCreditValue));
575 					}else if(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_MULTIPLE.equals(creditOption.getType())){
576 						Collections.sort(creditOption.getResultValues());
577 						StringBuilder sb = new StringBuilder(CourseAssemblerConstants.COURSE_RESULT_COMP_CREDIT_PREFIX);
578 						for(Iterator<String> iter = creditOption.getResultValues().iterator();iter.hasNext();){
579 							sb.append(iter.next());
580 							if(iter.hasNext()){
581 								sb.append(",");
582 							}
583 						}
584 						id = sb.toString();
585 						type = CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_MULTIPLE;
586 						resultValues = creditOption.getResultValues();
587 					}else if(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_VARIABLE.equals(creditOption.getType())){
588 					    /*
589 					     * For variable credits create a Result values that goes from min to max with the specified increment. 
590 					     * If no increment is specified, use 1.0 as the increment. The increment can be specified as a float.
591 					     */
592 					  					   
593 					    String minCreditValue = creditOption.getAttributes().get(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_MIN_CREDIT_VALUE);
594 						String maxCreditValue = creditOption.getAttributes().get(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_MAX_CREDIT_VALUE);
595 						String creditValueIncr = creditOption.getAttributes().get(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_CREDIT_VALUE_INCR);
596 						float minCredits = Float.parseFloat(minCreditValue);
597 						float maxCredits = Float.parseFloat(maxCreditValue);
598 												
599 						float increment = (null != creditValueIncr && creditValueIncr.length() > 0 ) ? Float.parseFloat(creditValueIncr) : 1.0f ;
600 												
601 						id = CourseAssemblerConstants.COURSE_RESULT_COMP_CREDIT_PREFIX + minCreditValue + "-" + maxCreditValue;
602 						type = CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_VARIABLE;
603 						resultValues = new ArrayList<String>();
604 						for(float i = minCredits; i <= maxCredits; i+=increment){
605 							resultValues.add(String.valueOf(i));
606 						}
607 						attributes = new HashMap<String,String>();
608 						attributes.put(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_MIN_CREDIT_VALUE, minCreditValue);
609 						attributes.put(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_MAX_CREDIT_VALUE, maxCreditValue);
610                         attributes.put(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_CREDIT_VALUE_INCR, creditValueIncr);
611 					}
612 	
613 					//Set the id
614 					creditOption.setId(id);
615 					
616 					//Create a new result component
617 					if(id != null && !rsltComps.contains(id)){
618 												
619 						//need to make a fixed degree result type component
620 						ResultComponentInfo resultComponent = new ResultComponentInfo();
621 						resultComponent.setId(id);
622 						resultComponent.setType(type);
623       resultComponent.setState ("Active");
624 						resultComponent.setResultValues(resultValues);
625 						resultComponent.setAttributes(attributes);
626 						BaseDTOAssemblyNode<ResultComponentInfo, ResultComponentInfo> node = new BaseDTOAssemblyNode<ResultComponentInfo, ResultComponentInfo>(null);
627 						node.setOperation(NodeOperation.CREATE);
628 						node.setNodeData(resultComponent);
629 						node.setBusinessDTORef(creditOption);
630 						results.add(node);
631 						
632 						rsltComps.add(id);
633 					}
634 				}
635 			}catch (NumberFormatException e){
636 				throw new AssemblyException("Invalid Arguments for credit outcome values",e);
637 			}catch (Exception e){
638 				throw new AssemblyException("Error Assembling", e);
639 			}
640 		}
641 		
642 		//Now do dissassembly for the actual clu-lrc relations and result options
643 		
644 		// Get the current options and put them in a map of option type id/cluResult
645 		Map<String, List<CluResultInfo>> currentResults = new HashMap<String, List<CluResultInfo>>();
646 		
647 		//If this is not a create, lookup the results for this clu
648 		if (!NodeOperation.CREATE.equals(operation)) {
649 			for (CluResultInfo currentResult : currentCluResults) {
650 				if (courseResultType.equals(currentResult.getType())) {
651 					//There should only be one grading option per CluResult for credit outcomes
652 					if(currentResult.getResultOptions().size()==1){
653 						//Create a mapping to a list of cluresults with the same result componentId
654 						String resultComponentId = currentResult.getResultOptions().get(0).getResultComponentId();
655 						if(!currentResults.containsKey(resultComponentId)){
656 							currentResults.put(resultComponentId, new ArrayList<CluResultInfo>());
657 						}
658 						currentResults.get(resultComponentId).add(currentResult);
659 					}else{
660 						LOG.warn("Credit Results should have exactly one result option each");
661 					}
662 				}
663 			}
664 		}
665 		
666 		//Loop through options on the course, if they are new, create a new cluResult
667 		for(ResultComponentInfo creditOption : course.getCreditOptions()){
668 		    if (NodeOperation.CREATE == operation
669 		            || (NodeOperation.UPDATE == operation && !currentResults.containsKey(creditOption.getId()) )) {
670 		    	
671 		    	ResultOptionInfo resultOption = new ResultOptionInfo();
672 		    	resultOption.setState(course.getState());
673 		    	resultOption.setResultComponentId(creditOption.getId());
674 		    	
675 		    	CluResultInfo cluResult = new CluResultInfo();
676 				cluResult.setCluId(clu.getId());
677 				cluResult.setState(course.getState());
678 				cluResult.setType(courseResultType);
679 				
680 				cluResult.getResultOptions().add(resultOption);
681 				
682 				BaseDTOAssemblyNode<ResultComponentInfo, CluResultInfo> cluResultNode = new BaseDTOAssemblyNode<ResultComponentInfo, CluResultInfo>(null);
683 				cluResultNode.setNodeData(cluResult);
684 				cluResultNode.setOperation(NodeOperation.CREATE);
685 				
686                 results.add(cluResultNode);
687             } else if (NodeOperation.UPDATE == operation
688 					&& currentResults.containsKey(creditOption.getId())) {
689             	//Get the list from the map and remove an entry, if the list is empty then remove it from the map
690             	List<CluResultInfo> cluResults = currentResults.get(creditOption.getId());
691             	cluResults.remove(cluResults.size()-1);
692             	if(cluResults.isEmpty()){
693             		currentResults.remove(creditOption.getId());
694             	}
695 			}
696 		}
697 		
698 		//Delete the leftovers
699 		for(Entry<String,List<CluResultInfo>> entry:currentResults.entrySet()){
700 			for(CluResultInfo cluResult:entry.getValue()){
701 				BaseDTOAssemblyNode<ResultComponentInfo, CluResultInfo> cluResultNode = new BaseDTOAssemblyNode<ResultComponentInfo, CluResultInfo>(null);
702 				cluResultNode.setNodeData(cluResult);
703 				cluResultNode.setOperation(NodeOperation.DELETE);
704 				results.add(cluResultNode);
705 			}
706 		}
707 		
708 		return results;
709 	}
710 
711 	private List<String> assembleGradingOptions(List<CluResultInfo> cluResults){
712 		
713 		String courseResultType = CourseAssemblerConstants.COURSE_RESULT_TYPE_GRADE;
714 		
715 		List<String> results = new ArrayList<String>();
716 		//Loop through all the CluResults to find the one with the matching type
717 		for(CluResultInfo cluResult:cluResults){
718 			if(courseResultType.equals(cluResult.getType())){
719 				//Loop through all options and add to the list of Strings
720 				for(ResultOptionInfo resultOption: cluResult.getResultOptions()){
721 					results.add(resultOption.getResultComponentId());
722 				}
723 				break;
724 			}
725 		}
726 		return results;
727 	}
728 	
729 	private List<ResultComponentInfo> assembleCreditOptions(
730 			List<CluResultInfo> cluResults) throws AssemblyException {
731 		String courseResultType = CourseAssemblerConstants.COURSE_RESULT_TYPE_CREDITS;
732 		List<ResultComponentInfo> results = new ArrayList<ResultComponentInfo>();
733 		//Loop through all the CluResults to find the one with the matching type
734 		for(CluResultInfo cluResult:cluResults){
735 			if(courseResultType.equals(cluResult.getType())){
736 				//Loop through all options and add to the list of Strings
737 				for(ResultOptionInfo resultOption: cluResult.getResultOptions()){
738 					try {
739 						ResultComponentInfo resultComponent = lrcService.getResultComponent(resultOption.getResultComponentId());
740 						results.add(resultComponent);
741 					} catch (DoesNotExistException e) {
742 						LOG.warn("Course Credit option:"+resultOption.getId()+" refers to non-existant ResultComponentInfo "+resultOption.getResultComponentId());
743 					} catch (Exception e) {
744 						throw new AssemblyException("Error getting result components",e);
745 					}
746 				}
747 			}
748 		}
749 		return results;
750 	}
751 	
752 	// TODO Use CluAssemblerUtils
753 	private List<BaseDTOAssemblyNode<?, ?>> disassembleLos(String cluId,
754 			CourseInfo course, NodeOperation operation) throws AssemblyException {
755 		// TODO Auto-generated method stub
756 		List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
757 
758 		// Get the current formats and put them in a map of format id/relation
759 		// id
760 		Map<String, CluLoRelationInfo> currentCluLoRelations = new HashMap<String, CluLoRelationInfo>();
761 		try {
762 			List<CluLoRelationInfo> cluLoRelations = luService.getCluLoRelationsByClu(cluId);
763 			for(CluLoRelationInfo cluLoRelation:cluLoRelations){
764 				if(CourseAssemblerConstants.COURSE_LO_COURSE_SPECIFIC_RELATION.equals(cluLoRelation.getType())){
765 					currentCluLoRelations.put(cluLoRelation.getLoId(), cluLoRelation);
766 				}
767 			}
768 		} catch (DoesNotExistException e) {
769 		} catch (Exception e) {
770 			throw new AssemblyException("Error finding related Los");
771 		}
772 		
773 		// Loop through all the los in this clu
774 		for(LoDisplayInfo loDisplay : course.getCourseSpecificLOs()){
775 
776 			// If this is a clu create/new lo update then all los will be created
777 		    if (NodeOperation.CREATE == operation
778 		            || (NodeOperation.UPDATE == operation &&  !currentCluLoRelations.containsKey(loDisplay.getLoInfo().getId()))) {
779 		        
780                 // the lo does not exist, so create
781                 // Assemble and add the lo
782 		    	loDisplay.getLoInfo().setId(null);
783                 BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
784                         .disassemble(loDisplay, NodeOperation.CREATE);
785                 results.add(loNode);
786 
787                 // Create the relationship and add it as well
788                 CluLoRelationInfo relation = new CluLoRelationInfo();
789                 relation.setCluId(cluId);
790                 relation.setLoId(loNode.getNodeData().getId());
791                 relation
792                         .setType(CourseAssemblerConstants.COURSE_LO_COURSE_SPECIFIC_RELATION);
793                 relation.setState(course.getState());
794 
795                 BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo> relationNode = new BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo>(
796                         null);
797                 relationNode.setNodeData(relation);
798                 relationNode.setOperation(NodeOperation.CREATE);
799 
800                 results.add(relationNode);
801             } else if (NodeOperation.UPDATE == operation
802 					&& currentCluLoRelations.containsKey(loDisplay.getLoInfo().getId())) {
803 				// If the clu already has this lo, then just update the lo
804                 BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
805                 		.disassemble(loDisplay, NodeOperation.UPDATE);
806 				results.add(loNode);
807 
808 				// remove this entry from the map so we can tell what needs to
809 				// be deleted at the end
810 				currentCluLoRelations.remove(loDisplay.getLoInfo().getId());
811 			} else if (NodeOperation.DELETE == operation
812                     && currentCluLoRelations.containsKey(loDisplay.getLoInfo().getId())) {
813 			    
814                 // Delete the Format and its relation
815 				CluLoRelationInfo relationToDelete = currentCluLoRelations.get(loDisplay.getLoInfo().getId());
816                 BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo>(
817                         null);
818                 relationToDeleteNode.setNodeData(relationToDelete);
819                 relationToDeleteNode.setOperation(NodeOperation.DELETE);
820                 results.add(relationToDeleteNode);
821             
822                 BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
823         				.disassemble(loDisplay, NodeOperation.DELETE);
824                 results.add(loNode);                                
825 
826                 // remove this entry from the map so we can tell what needs to
827                 // be deleted at the end
828                 currentCluLoRelations.remove(loDisplay.getLoInfo().getId());			    
829 			}
830 		}         
831 
832         // Now any leftover lo ids are no longer needed, so delete
833         // los and relations
834         for (Entry<String, CluLoRelationInfo> entry : currentCluLoRelations.entrySet()) {
835             // Create a new relation with the id of the relation we want to
836             // delete
837         	CluLoRelationInfo relationToDelete = entry.getValue();
838             BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo>(
839                     null);
840             relationToDeleteNode.setNodeData(relationToDelete);
841             relationToDeleteNode.setOperation(NodeOperation.DELETE);
842             results.add(relationToDeleteNode);
843 
844             try{
845             	LoInfo loToDelete = loService.getLo(entry.getKey());
846             
847 	            LoDisplayInfo loDisplayToDelete = loAssembler.assemble(loToDelete, null, false);
848 	            BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
849 	            		.disassemble(loDisplayToDelete, NodeOperation.DELETE);
850 	            results.add(loNode);
851 	        } catch (DoesNotExistException e){
852 	        	LOG.warn("Trying to delete non exsistant LO:"+entry.getKey());
853             } catch (Exception e) {
854 				throw new AssemblyException("Error disassembling LOs",e);
855 			}
856         }
857 		
858 		return results;
859 	}
860 	
861 	private BaseDTOAssemblyNode<?, ?> disassembleGradingOptions(String cluId,
862 			String courseState, List<String> options, List<CluResultInfo> currentCluResults, NodeOperation operation) throws AssemblyException {
863 		BaseDTOAssemblyNode<List<String>, CluResultInfo> cluResultNode = new BaseDTOAssemblyNode<List<String>, CluResultInfo>(null);
864 				
865 		String courseResultType=CourseAssemblerConstants.COURSE_RESULT_TYPE_GRADE; 
866 		String resultsDescription="Grading options";
867 		String resultDescription="Grading option";
868 
869 		// Get the current options and put them in a map of option type id/cluResult
870 		Map<String, ResultOptionInfo> currentResults = new HashMap<String, ResultOptionInfo>();
871 
872 		CluResultInfo cluResult = null;
873 		
874 		//If this is not a create, lookup the results for this clu
875 		if (!NodeOperation.CREATE.equals(operation)) {
876 			for (CluResultInfo currentResult : currentCluResults) {
877 				if (courseResultType.equals(currentResult.getType())) {
878 					cluResult = currentResult;
879 					if(NodeOperation.DELETE.equals(operation)){
880 						//if this is a delete, then we only need the CluResultInfo
881 						cluResultNode.setOperation(NodeOperation.DELETE);
882 					}else{
883 						//Find all the Result options and store in a map for easy access later
884 						cluResultNode.setOperation(NodeOperation.UPDATE);
885 						for(ResultOptionInfo resultOption:currentResult.getResultOptions()){
886 							currentResults.put(resultOption.getResultComponentId(), resultOption);
887 						}
888 					}
889 					break;
890 				}
891 			}
892 		}
893 
894 		//If this is a delete we don't need the result options, just the CluResultInfo
895 		if(!NodeOperation.DELETE.equals(operation)){
896 			if(cluResult == null){
897 				//Create a new resultInfo of the given type if one does not exist and set operation to Create
898 				cluResult = new CluResultInfo();
899 				cluResult.setCluId(cluId);
900 				cluResult.setState(courseState);
901 				cluResult.setType(courseResultType);
902 				RichTextInfo desc = new RichTextInfo();
903 				desc.setPlain(resultsDescription);
904 				cluResult.setDesc(desc);
905 				cluResult.setEffectiveDate(new Date());
906 				cluResultNode.setOperation(NodeOperation.CREATE);
907 			}
908 	
909 			cluResult.setResultOptions(new ArrayList<ResultOptionInfo>());
910 	
911 			// Loop through all the credit options in this course
912 			for (String optionType : options) {
913 				if(currentResults.containsKey(optionType)){
914 					//If the option exists already copy it to the new list of result options
915 					ResultOptionInfo resultOptionInfo = currentResults.get(optionType);
916 					cluResult.getResultOptions().add(resultOptionInfo);
917 				}else{
918 					//Otherwise create a new result option
919 					ResultOptionInfo resultOptionInfo = new ResultOptionInfo();
920 					RichTextInfo desc = new RichTextInfo();
921 					desc.setPlain(resultDescription);
922 					resultOptionInfo.setDesc(desc);
923 					resultOptionInfo.setResultComponentId(optionType);
924 					resultOptionInfo.setState(courseState);
925 					
926 					cluResult.getResultOptions().add(resultOptionInfo);
927 				}
928 			}
929 		}
930 		
931 		cluResultNode.setNodeData(cluResult);
932 		return cluResultNode;
933 	}
934 
935 	// TODO This is pretty much a copy of the FormatAssembler's
936 	// disassembleActivities code... maybe can be made generic
937 	private List<BaseDTOAssemblyNode<?, ?>> disassembleFormats(String nodeId,
938 			CourseInfo course, NodeOperation operation)
939 			throws AssemblyException, DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException {
940 
941 		List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
942 
943 		// Get the current formats and put them in a map of format id/relation
944 		// id
945 		Map<String, String> currentformatIds = new HashMap<String, String>();
946 
947 		if (!NodeOperation.CREATE.equals(operation)) {
948 			try {
949 				List<CluCluRelationInfo> formatRelationships = luService
950 						.getCluCluRelationsByClu(course.getId());
951 				
952 				//formatRelationships = (null == formatRelationships) ? new ArrayList<CluCluRelationInfo>() : formatRelationships;
953 				
954 				for (CluCluRelationInfo formatRelation : formatRelationships) {
955 					if (CourseAssemblerConstants.COURSE_FORMAT_RELATION_TYPE
956 							.equals(formatRelation.getType())) {
957 						currentformatIds.put(formatRelation.getRelatedCluId(),
958 								formatRelation.getId());
959 					}
960 				}
961 			} catch (DoesNotExistException e) {
962 			} catch (InvalidParameterException e) {
963 				throw new AssemblyException("Error getting related formats", e);
964 			} catch (MissingParameterException e) {
965 				throw new AssemblyException("Error getting related formats", e);
966 			} catch (OperationFailedException e) {
967 				throw new AssemblyException("Error getting related formats", e);
968 			}
969 		}
970 
971 		// Loop through all the formats in this course
972 		for (FormatInfo format : course.getFormats()) {
973 
974 		    //  If this is a course create/new format update then all formats will be created
975 		    if (NodeOperation.CREATE == operation
976 		            || (NodeOperation.UPDATE == operation && !currentformatIds.containsKey(format.getId()) )) {
977                 // the format does not exist, so create
978                 // Assemble and add the format
979                 BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
980                         .disassemble(format, NodeOperation.CREATE);
981                 results.add(formatNode);
982 
983                 // Create the relationship and add it as well
984                 CluCluRelationInfo relation = new CluCluRelationInfo();
985                 relation.setCluId(nodeId);
986                 relation.setRelatedCluId(formatNode.getNodeData().getId());// this
987                 // should
988                 // already
989                 // be set even if
990                 // it's a create
991                 relation
992                         .setType(CourseAssemblerConstants.COURSE_FORMAT_RELATION_TYPE);
993                 relation.setState(course.getState());
994 
995                 BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo> relationNode = new BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo>(
996                         null);
997                 relationNode.setNodeData(relation);
998                 relationNode.setOperation(NodeOperation.CREATE);
999 
1000                 results.add(relationNode);
1001             } else if (NodeOperation.UPDATE == operation
1002 					&& currentformatIds.containsKey(format.getId())) {
1003 				// If the course already has this format, then just update the
1004 				// format
1005 				BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
1006 						.disassemble(format, NodeOperation.UPDATE);
1007 				results.add(formatNode);
1008 
1009 				// remove this entry from the map so we can tell what needs to
1010 				// be deleted at the end
1011 				currentformatIds.remove(format.getId());
1012 			} else if (NodeOperation.DELETE == operation
1013                     && currentformatIds.containsKey(format.getId()))  {
1014 			    // Delete the Format and its relation
1015 	            CluCluRelationInfo relationToDelete = new CluCluRelationInfo();
1016 	            relationToDelete.setId( currentformatIds.get(format.getId()) );
1017 	            BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo>(
1018 	                    null);
1019 	            relationToDeleteNode.setNodeData(relationToDelete);
1020 	            relationToDeleteNode.setOperation(NodeOperation.DELETE);
1021 	            results.add(relationToDeleteNode);
1022 			
1023                 BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
1024                 .disassemble(format, NodeOperation.DELETE);
1025                 results.add(formatNode);	            	            
1026 
1027                 // remove this entry from the map so we can tell what needs to
1028                 // be deleted at the end
1029                 currentformatIds.remove(format.getId());
1030 			}			    
1031 		}
1032 		
1033         // Now any leftover format ids are no longer needed, so delete
1034         // formats and relations. These formats have to be assembled first before they can be marked for deletion
1035         for (Entry<String, String> entry : currentformatIds.entrySet()) {
1036             // Create a new relation with the id of the relation we want to
1037             // delete
1038             CluCluRelationInfo relationToDelete = new CluCluRelationInfo();
1039             relationToDelete.setId( entry.getValue() );
1040             BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo>(
1041                     null);
1042             relationToDeleteNode.setNodeData(relationToDelete);
1043             relationToDeleteNode.setOperation(NodeOperation.DELETE);
1044             results.add(relationToDeleteNode);
1045             
1046             CluInfo formatCluToDelete = luService.getClu(entry.getKey());
1047             FormatInfo formatToDelete = formatAssembler.assemble(formatCluToDelete, null, false);
1048             BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
1049             .disassemble(formatToDelete, NodeOperation.DELETE);
1050             results.add(formatNode);                                            
1051         }
1052 
1053 		return results;
1054 	}
1055 	
1056 	private List<CourseVariationInfo> assembleVariations(List<CluIdentifierInfo> cluIdents) {
1057 		List<CourseVariationInfo> variations = new ArrayList<CourseVariationInfo>();
1058 		if (cluIdents != null) {
1059 			for (CluIdentifierInfo cluIdent : cluIdents) {
1060 				if (cluIdent.getType() != null && 
1061 						cluIdent.getType().equals(CourseAssemblerConstants.COURSE_VARIATION_IDENT_TYPE)) {
1062 					CourseVariationInfo variation = new CourseVariationInfo();
1063 					variation.setId(cluIdent.getId());
1064 					variation.setType(cluIdent.getType());
1065 					variation.setCourseNumberSuffix(cluIdent.getSuffixCode());
1066 					variation.setSubjectArea(cluIdent.getDivision());
1067 					variation.setVariationCode(cluIdent.getVariation());
1068 					variation.setVariationTitle(cluIdent.getLongName());
1069 					variations.add(variation);
1070 				}
1071 			}
1072 		}
1073 		return variations;
1074 	}
1075 	
1076 	private List<CourseCrossListingInfo> assembleCrossListings(List<CluIdentifierInfo> cluIdents) {
1077 		List<CourseCrossListingInfo> crossListings = new ArrayList<CourseCrossListingInfo>();
1078 		if (cluIdents != null) {
1079 			for (CluIdentifierInfo cluIdent : cluIdents) {
1080 				if (cluIdent.getType() != null && 
1081 						cluIdent.getType().equals(CourseAssemblerConstants.COURSE_CROSSLISTING_IDENT_TYPE)) {
1082 					CourseCrossListingInfo crosslisting = new CourseCrossListingInfo();
1083 					crosslisting.setId(cluIdent.getId());
1084 					crosslisting.setType(cluIdent.getType());
1085 					crosslisting.setCourseNumberSuffix(cluIdent.getSuffixCode());
1086 					crosslisting.setSubjectArea(cluIdent.getDivision());
1087 					crosslisting.setDepartment(cluIdent.getOrgId());
1088 					crossListings.add(crosslisting);
1089 				}
1090 			}
1091 		}
1092 		return crossListings;
1093 	}
1094 	
1095 	// TODO This is pretty much a copy of the disassembleJoints
1096 	// code... maybe can be made generic
1097 	private List<BaseDTOAssemblyNode<?, ?>> disassembleJoints(String nodeId,
1098 			CourseInfo course, NodeOperation operation)
1099 			throws AssemblyException {
1100 
1101 		List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
1102 
1103 		// Get the current joints and put them in a map of joint id/relation
1104 		// id
1105 		Map<String, CluCluRelationInfo> currentJointIds = new HashMap<String, CluCluRelationInfo>();
1106 
1107 		if (!NodeOperation.CREATE.equals(operation)) {
1108 			try {
1109 				List<CluCluRelationInfo> jointRelationships = luService
1110 						.getCluCluRelationsByClu(course.getId());
1111 				for (CluCluRelationInfo jointRelation : jointRelationships) {
1112 					if (CourseAssemblerConstants.JOINT_RELATION_TYPE
1113 							.equals(jointRelation.getType())) {
1114 						currentJointIds.put(jointRelation.getId(),jointRelation);
1115 					}
1116 				}
1117 			} catch (DoesNotExistException e) {
1118 			} catch (InvalidParameterException e) {
1119 				throw new AssemblyException("Error getting related formats", e);
1120 			} catch (MissingParameterException e) {
1121 				throw new AssemblyException("Error getting related formats", e);
1122 			} catch (OperationFailedException e) {
1123 				throw new AssemblyException("Error getting related formats", e);
1124 			}
1125 		}
1126 
1127 		// Loop through all the joints in this course
1128 		for (CourseJointInfo joint : course.getJoints()) {
1129 
1130 			// If this is a course create then all joints will be created
1131 			if (NodeOperation.UPDATE.equals(operation) && joint.getRelationId() != null
1132 					&& currentJointIds.containsKey(joint.getRelationId())) {
1133 				// remove this entry from the map so we can tell what needs to
1134 				// be deleted at the end
1135 				CluCluRelationInfo relation = currentJointIds.remove(joint.getRelationId());
1136 				relation.setRelatedCluId(joint.getCourseId());
1137 				BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo> jointNode = new BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo>(courseJointAssembler);
1138 				jointNode.setBusinessDTORef(joint);
1139 				jointNode.setNodeData(relation);
1140 				jointNode.setOperation(NodeOperation.UPDATE);
1141 				results.add(jointNode);
1142 			} else if (!NodeOperation.DELETE.equals(operation)) {
1143 				// the joint does not exist, so create cluclurelation
1144 				BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo> jointNode = courseJointAssembler
1145 						.disassemble(joint, NodeOperation.CREATE);
1146 				jointNode.getNodeData().setCluId(nodeId);
1147 				results.add(jointNode);
1148 			}
1149 		}
1150 
1151         // Now any leftover joint ids are no longer needed, so delete
1152         // joint relations
1153         for (String id : currentJointIds.keySet()) {
1154             // Create a new relation with the id of the relation we want to
1155             // delete
1156             CluCluRelationInfo relationToDelete = new CluCluRelationInfo();
1157             relationToDelete.setId(id);
1158             BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo>(
1159                     courseJointAssembler);
1160             relationToDeleteNode.setNodeData(relationToDelete);
1161             relationToDeleteNode.setOperation(NodeOperation.DELETE);
1162             results.add(relationToDeleteNode);
1163         }
1164 		
1165 		return results;
1166 	}
1167 
1168 	public void setLuService(LuService luService) {
1169 		this.luService = luService;
1170 	}
1171 
1172 	public void setFormatAssembler(FormatAssembler formatAssembler) {
1173 		this.formatAssembler = formatAssembler;
1174 	}
1175 
1176 	public void setCourseJointAssembler(
1177 			CourseJointAssembler courseJointAssembler) {
1178 		this.courseJointAssembler = courseJointAssembler;
1179 	}
1180 
1181 	public void setLoAssembler(LoAssembler loAssembler) {
1182 		this.loAssembler = loAssembler;
1183 	}
1184 
1185 	public void setLoService(LearningObjectiveService loService) {
1186 		this.loService = loService;
1187 	}
1188 
1189     public void setCluAssemblerUtils(CluAssemblerUtils cluAssemblerUtils) {
1190         this.cluAssemblerUtils = cluAssemblerUtils;
1191     }
1192 
1193 	public void setLrcService(LrcService lrcService) {
1194 		this.lrcService = lrcService;
1195 	}
1196 }