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