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(StringUtils.hasText(course.getCourseNumberSuffix()) && StringUtils.hasText(course.getSubjectArea())){
316 			identifier.setCode(calculateCourseCode(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 ings
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 			cluIdentifier.setAttributes(crossListing.getAttributes());
366 
367 	        //Custom logic to set the code as the concatenation of division and course number suffix if code not provided
368 	        if (StringUtils.hasText(crossListing.getCode())){
369 	            cluIdentifier.setCode(crossListing.getCode());
370 	        } else if(StringUtils.hasText(crossListing.getCourseNumberSuffix()) && StringUtils.hasText(crossListing.getSubjectArea())){
371 	            cluIdentifier.setCode(calculateCourseCode(crossListing.getSubjectArea(), crossListing.getCourseNumberSuffix()));         
372 	        }else{
373 	            cluIdentifier.setCode(null);
374 	        }
375 	        			
376 			clu.getAlternateIdentifiers().add(cluIdentifier);
377 		}
378 
379 		List<AdminOrgInfo> adminOrgInfos = new ArrayList<AdminOrgInfo>();
380 		for(String org:course.getUnitsDeployment()){
381 			AdminOrgInfo adminOrg = new AdminOrgInfo();
382 			adminOrg.setType(CourseAssemblerConstants.ADMIN_ORG);
383 			adminOrg.setOrgId(org);
384 			adminOrgInfos.add(adminOrg);
385 		}
386 		clu.getAdminOrgs().addAll(adminOrgInfos);
387 		
388 		List<AdminOrgInfo> subjectOrgs = new ArrayList<AdminOrgInfo>();
389 		for (String subOrg : course.getUnitsContentOwner()) {
390 			AdminOrgInfo subjectOrg = new AdminOrgInfo();
391 			subjectOrg.setType(CourseAssemblerConstants.SUBJECT_ORG);
392 			subjectOrg.setOrgId(subOrg);
393 			subjectOrgs.add(subjectOrg);
394 		}
395 		clu.getAdminOrgs().addAll(subjectOrgs);
396 
397 		
398 		clu.setAttributes(course.getAttributes());
399 		clu.setCampusLocations(course.getCampusLocations());
400 		clu.setDescr(course.getDescr());
401 		clu.setStdDuration(course.getDuration());
402 		clu.setEffectiveDate(course.getEffectiveDate());
403 		clu.setExpirationDate(course.getExpirationDate());
404 
405 		clu.setOfferedAtpTypes(course.getTermsOffered());
406 		clu.setPrimaryInstructor(course.getPrimaryInstructor());
407 		
408 		clu.setIntensity(course.getOutOfClassHours());
409 		clu.setInstructors(course.getInstructors());
410 		
411 		clu.setExpectedFirstAtp(course.getStartTerm());
412 		clu.setLastAtp(course.getEndTerm());
413 		
414 		clu.setMetaInfo(course.getMetaInfo());
415 		clu.setVersionInfo(course.getVersionInfo());
416 
417 		// Add the Clu to the result
418 		result.setNodeData(clu);
419 		result.setOperation(operation);
420 		result.setBusinessDTORef(course);
421 
422 		// Use the Format assembler to disassemble the formats and relations
423 		List<BaseDTOAssemblyNode<?, ?>> formatResults;
424         try {
425             formatResults = disassembleFormats(clu
426             		.getId(), course, operation);
427             result.getChildNodes().addAll(formatResults);
428             
429         } catch (DoesNotExistException e) {
430         } catch (Exception e) {
431             throw new AssemblyException("Error while disassembling format", e);
432         }
433 
434 		// Use the CourseJoint assembler to disassemble the CourseJoints and
435 		// relations
436 		List<BaseDTOAssemblyNode<?, ?>> courseJointResults = disassembleJoints(
437 				clu.getId(), course, operation);
438 		result.getChildNodes().addAll(courseJointResults);
439 
440 		//Disassemble the CluResults (grading and credit options)
441 		//Special code to take audit from attributes and put into options
442 		if(course.getAttributes().containsKey(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_AUDIT)&&"true".equals(course.getAttributes().get(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_AUDIT))){
443 			if(!course.getGradingOptions().contains(CourseAssemblerConstants.COURSE_RESULT_COMP_GRADE_AUDIT)){
444 				course.getGradingOptions().add(CourseAssemblerConstants.COURSE_RESULT_COMP_GRADE_AUDIT);
445 			}
446 		}
447 		
448 		List<CluResultInfo> cluResultList;
449 		try {
450 			cluResultList = luService.getCluResultByClu(clu.getId());
451 		} catch (DoesNotExistException e) {
452 			cluResultList = Collections.emptyList();
453 		} catch (Exception e) {
454 			throw new AssemblyException("Error getting cluResults", e);
455 		}
456 		
457 		List<BaseDTOAssemblyNode<?, ?>> creditOutcomes = disassembleCreditOutcomes(course, clu, cluResultList, operation);
458 		result.getChildNodes().addAll(creditOutcomes);
459 		
460 		BaseDTOAssemblyNode<?, ?> gradingOptions = disassembleGradingOptions(
461 				clu.getId(), course.getState(), course.getGradingOptions(), cluResultList, operation);
462 		result.getChildNodes().add(gradingOptions);
463 		
464 		//Use the LoAssembler to disassemble Los
465         try {
466     		List<BaseDTOAssemblyNode<?, ?>> loResults;
467     		loResults = disassembleLos(clu.getId(), course, operation);
468             result.getChildNodes().addAll(loResults);
469         } catch (Exception e) {
470             throw new AssemblyException("Error while disassembling los", e);
471         }
472 		
473 		//add the special topics code if it did not exist, or remove if it was not wanted
474 		boolean alreadyHadSpecialTopicsCode = false;
475 		for(Iterator<LuCodeInfo> luCodeIterator = clu.getLuCodes().iterator();luCodeIterator.hasNext();){
476 			LuCodeInfo luCode = luCodeIterator.next();
477 			if(CourseAssemblerConstants.COURSE_CODE_SPECIAL_TOPICS.equals(luCode.getType())){
478 				alreadyHadSpecialTopicsCode = true;
479 				if(!course.isSpecialTopicsCourse()){
480 					luCodeIterator.remove();
481 				}
482 				break;
483 			}
484 		}
485 		if(!alreadyHadSpecialTopicsCode && course.isSpecialTopicsCourse()){
486 			LuCodeInfo luCode = new LuCodeInfo();
487 			luCode.setType(CourseAssemblerConstants.COURSE_CODE_SPECIAL_TOPICS);
488 			luCode.setValue("true");
489 			clu.getLuCodes().add(luCode);
490 		}
491 		
492 		//add the special topics code if it did not exist, or remove if it was not wanted
493 		boolean alreadyHadPilotCourseCode = false;
494 		for(Iterator<LuCodeInfo> luCodeIterator = clu.getLuCodes().iterator();luCodeIterator.hasNext();){
495 			LuCodeInfo luCode = luCodeIterator.next();
496 			if(CourseAssemblerConstants.COURSE_CODE_PILOT_COURSE.equals(luCode.getType())){
497 				alreadyHadPilotCourseCode = true;
498 				if(!course.isPilotCourse()){
499 					luCodeIterator.remove();
500 				}
501 				break;
502 			}
503 		}
504 		if(!alreadyHadPilotCourseCode && course.isPilotCourse()){
505 			LuCodeInfo luCode = new LuCodeInfo();
506 			luCode.setType(CourseAssemblerConstants.COURSE_CODE_PILOT_COURSE);
507 			luCode.setValue("true");
508 			clu.getLuCodes().add(luCode);
509 		}
510 		
511 		//FEES
512 		if(clu.getFeeInfo() == null){
513 			clu.setFeeInfo(new CluFeeInfo());
514 		}
515 		clu.getFeeInfo().setDescr(course.getFeeJustification());
516 		clu.getFeeInfo().getCluFeeRecords().clear();
517 		for(CourseRevenueInfo courseRevenue:course.getRevenues()){
518 			CluFeeRecordInfo cluFeeRecord  = new CluFeeRecordInfo();
519 			cluFeeRecord.setFeeType(CourseAssemblerConstants.COURSE_FINANCIALS_REVENUE_TYPE);
520 			cluFeeRecord.setRateType(CourseAssemblerConstants.COURSE_FINANCIALS_REVENUE_TYPE);
521 			cluFeeRecord.setAttributes(courseRevenue.getAttributes());
522 			cluFeeRecord.setAffiliatedOrgs(courseRevenue.getAffiliatedOrgs());
523 			cluFeeRecord.setId(courseRevenue.getId());
524 			cluFeeRecord.setMetaInfo(courseRevenue.getMetaInfo());
525 			clu.getFeeInfo().getCluFeeRecords().add(cluFeeRecord);
526 		}
527 		for(CourseFeeInfo courseFee : course.getFees()){
528 			CluFeeRecordInfo cluFeeRecord  = new CluFeeRecordInfo();
529 			cluFeeRecord.setFeeType(courseFee.getFeeType());
530 			cluFeeRecord.setRateType(courseFee.getRateType());
531 			cluFeeRecord.setDescr(courseFee.getDescr());
532 			cluFeeRecord.setMetaInfo(courseFee.getMetaInfo());
533 			cluFeeRecord.setId(courseFee.getId());
534 			cluFeeRecord.setFeeAmounts(courseFee.getFeeAmounts());
535 			cluFeeRecord.setAttributes(courseFee.getAttributes());
536 			clu.getFeeInfo().getCluFeeRecords().add(cluFeeRecord);
537 		}
538 		if(clu.getAccountingInfo() == null || course.getExpenditure()== null){
539 			clu.setAccountingInfo( new CluAccountingInfo());
540 		}
541 		if(course.getExpenditure() != null){
542 			clu.getAccountingInfo().setAffiliatedOrgs(course.getExpenditure().getAffiliatedOrgs());
543 			clu.getAccountingInfo().setAttributes(course.getExpenditure().getAttributes());
544 		}
545 		
546 		return result;
547 	}
548 
549 	private List<BaseDTOAssemblyNode<?, ?>> disassembleCreditOutcomes(CourseInfo course, CluInfo clu, List<CluResultInfo> currentCluResults, NodeOperation operation) throws AssemblyException, NumberFormatException {
550 		
551 		List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
552 		
553 		String courseResultType = CourseAssemblerConstants.COURSE_RESULT_TYPE_CREDITS;
554 		
555 		//See if we need to create any new lrcs
556 		if(NodeOperation.DELETE!=operation){
557 			//Find all the existing LRCs for the following three types
558 			Set<String> rsltComps = new HashSet<String>();
559 			
560 			try{
561 				try {
562 					rsltComps.addAll(lrcService.getResultComponentIdsByResultComponentType(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_FIXED));
563 				} catch (DoesNotExistException e) {}
564 				try {
565 					rsltComps.addAll(lrcService.getResultComponentIdsByResultComponentType(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_MULTIPLE));
566 				} catch (DoesNotExistException e) {}
567 				try {
568 					rsltComps.addAll(lrcService.getResultComponentIdsByResultComponentType(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_VARIABLE));
569 				} catch (DoesNotExistException e) {}
570 
571 				//Create any LRCs that do not yet exist
572 				for(ResultComponentInfo creditOption:course.getCreditOptions()){
573 					String id = null;
574 					String type = null;
575 					List<String> resultValues = null;
576 					Map<String,String> attributes = null;
577 					//Depending on the type, set the id, type and result values differently
578 					if(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_FIXED.equals(creditOption.getType())){
579 						float fixedCreditValue = Float.parseFloat(creditOption.getAttributes().get(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_FIXED_CREDIT_VALUE));
580 						id = CourseAssemblerConstants.COURSE_RESULT_COMP_CREDIT_PREFIX + fixedCreditValue;
581 						type = CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_FIXED;
582 						resultValues = new ArrayList<String>();
583 						resultValues.add(String.valueOf(fixedCreditValue));
584 						attributes = new HashMap<String,String>();
585 						attributes.put(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_FIXED_CREDIT_VALUE, String.valueOf(fixedCreditValue));
586 					}else if(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_MULTIPLE.equals(creditOption.getType())){
587 						Collections.sort(creditOption.getResultValues());
588 						StringBuilder sb = new StringBuilder(CourseAssemblerConstants.COURSE_RESULT_COMP_CREDIT_PREFIX);
589 						for(Iterator<String> iter = creditOption.getResultValues().iterator();iter.hasNext();){
590 							sb.append(iter.next());
591 							if(iter.hasNext()){
592 								sb.append(",");
593 							}
594 						}
595 						id = sb.toString();
596 						type = CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_MULTIPLE;
597 						resultValues = creditOption.getResultValues();
598 					}else if(CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_VARIABLE.equals(creditOption.getType())){
599 					    /*
600 					     * For variable credits create a Result values that goes from min to max with the specified increment. 
601 					     * If no increment is specified, use 1.0 as the increment. The increment can be specified as a float.
602 					     */
603 					  					   
604 					    String minCreditValue = creditOption.getAttributes().get(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_MIN_CREDIT_VALUE);
605 						String maxCreditValue = creditOption.getAttributes().get(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_MAX_CREDIT_VALUE);
606 						String creditValueIncr = creditOption.getAttributes().get(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_CREDIT_VALUE_INCR);
607 						float minCredits = Float.parseFloat(minCreditValue);
608 						float maxCredits = Float.parseFloat(maxCreditValue);
609 												
610 						float increment = (null != creditValueIncr && creditValueIncr.length() > 0 ) ? Float.parseFloat(creditValueIncr) : 1.0f ;
611 												
612 						id = CourseAssemblerConstants.COURSE_RESULT_COMP_CREDIT_PREFIX + minCreditValue + "-" + maxCreditValue;
613 						type = CourseAssemblerConstants.COURSE_RESULT_COMP_TYPE_CREDIT_VARIABLE;
614 						resultValues = new ArrayList<String>();
615 						for(float i = minCredits; i <= maxCredits; i+=increment){
616 							resultValues.add(String.valueOf(i));
617 						}
618 						attributes = new HashMap<String,String>();
619 						attributes.put(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_MIN_CREDIT_VALUE, minCreditValue);
620 						attributes.put(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_MAX_CREDIT_VALUE, maxCreditValue);
621                         attributes.put(CourseAssemblerConstants.COURSE_RESULT_COMP_ATTR_CREDIT_VALUE_INCR, creditValueIncr);
622 					}
623 	
624 					//Set the id
625 					creditOption.setId(id);
626 					
627 					//Create a new result component
628 					if(id != null && !rsltComps.contains(id)){
629 												
630 						//need to make a fixed degree result type component
631 						ResultComponentInfo resultComponent = new ResultComponentInfo();
632 						resultComponent.setId(id);
633 						resultComponent.setType(type);
634 						//resultComponent.setState ("Active");
635 						resultComponent.setState (course.getState());
636 						resultComponent.setResultValues(resultValues);
637 						resultComponent.setAttributes(attributes);
638 						BaseDTOAssemblyNode<ResultComponentInfo, ResultComponentInfo> node = new BaseDTOAssemblyNode<ResultComponentInfo, ResultComponentInfo>(null);
639 						node.setOperation(NodeOperation.CREATE);
640 						node.setNodeData(resultComponent);
641 						node.setBusinessDTORef(creditOption);
642 						results.add(node);
643 						
644 						rsltComps.add(id);
645 					}
646 				}
647 			}catch (NumberFormatException e){
648 				throw new AssemblyException("Invalid Arguments for credit outcome values",e);
649 			}catch (Exception e){
650 				throw new AssemblyException("Error Assembling", e);
651 			}
652 		}
653 		
654 		//Now do dissassembly for the actual clu-lrc relations and result options
655 		
656 		// Get the current options and put them in a map of option type id/cluResult
657 		Map<String, List<CluResultInfo>> currentResults = new HashMap<String, List<CluResultInfo>>();
658 		
659 		//If this is not a create, lookup the results for this clu
660 		if (!NodeOperation.CREATE.equals(operation)) {
661 			for (CluResultInfo currentResult : currentCluResults) {
662 				if (courseResultType.equals(currentResult.getType())) {
663 					//There should only be one grading option per CluResult for credit outcomes
664 					if(currentResult.getResultOptions().size()==1){
665 						//Create a mapping to a list of cluresults with the same result componentId
666 						String resultComponentId = currentResult.getResultOptions().get(0).getResultComponentId();
667 						if(!currentResults.containsKey(resultComponentId)){
668 							currentResults.put(resultComponentId, new ArrayList<CluResultInfo>());
669 						}
670 						currentResults.get(resultComponentId).add(currentResult);
671 					}else{
672 						LOG.warn("Credit Results should have exactly one result option each");
673 					}
674 				}
675 			}
676 		}
677 		
678 		//Loop through options on the course, if they are new, create a new cluResult
679 		for(ResultComponentInfo creditOption : course.getCreditOptions()){
680 		    if (NodeOperation.CREATE == operation
681 		            || (NodeOperation.UPDATE == operation && !currentResults.containsKey(creditOption.getId()) )) {
682 		    	
683 		    	ResultOptionInfo resultOption = new ResultOptionInfo();
684 		    	resultOption.setState(course.getState());
685 		    	resultOption.setResultComponentId(creditOption.getId());
686 		    	
687 		    	CluResultInfo cluResult = new CluResultInfo();
688 				cluResult.setCluId(clu.getId());
689 				cluResult.setState(course.getState());
690 				cluResult.setType(courseResultType);
691 				
692 				cluResult.getResultOptions().add(resultOption);
693 				
694 				BaseDTOAssemblyNode<ResultComponentInfo, CluResultInfo> cluResultNode = new BaseDTOAssemblyNode<ResultComponentInfo, CluResultInfo>(null);
695 				cluResultNode.setNodeData(cluResult);
696 				cluResultNode.setOperation(NodeOperation.CREATE);
697 				
698                 results.add(cluResultNode);
699             } else if (NodeOperation.UPDATE == operation
700 					&& currentResults.containsKey(creditOption.getId())) {
701             	//Get the list from the map and remove an entry, if the list is empty then remove it from the map
702             	List<CluResultInfo> cluResults = currentResults.get(creditOption.getId());
703             	cluResults.remove(cluResults.size()-1);
704             	if(cluResults.isEmpty()){
705             		currentResults.remove(creditOption.getId());
706             	}
707 			}
708 		}
709 		
710 		//Delete the leftovers
711 		for(Entry<String,List<CluResultInfo>> entry:currentResults.entrySet()){
712 			for(CluResultInfo cluResult:entry.getValue()){
713 				BaseDTOAssemblyNode<ResultComponentInfo, CluResultInfo> cluResultNode = new BaseDTOAssemblyNode<ResultComponentInfo, CluResultInfo>(null);
714 				cluResultNode.setNodeData(cluResult);
715 				cluResultNode.setOperation(NodeOperation.DELETE);
716 				results.add(cluResultNode);
717 			}
718 		}
719 		
720 		return results;
721 	}
722 
723 	private List<String> assembleGradingOptions(List<CluResultInfo> cluResults){
724 		
725 		String courseResultType = CourseAssemblerConstants.COURSE_RESULT_TYPE_GRADE;
726 		
727 		List<String> results = new ArrayList<String>();
728 		//Loop through all the CluResults to find the one with the matching type
729 		for(CluResultInfo cluResult:cluResults){
730 			if(courseResultType.equals(cluResult.getType())){
731 				//Loop through all options and add to the list of Strings
732 				for(ResultOptionInfo resultOption: cluResult.getResultOptions()){
733 					results.add(resultOption.getResultComponentId());
734 				}
735 				break;
736 			}
737 		}
738 		return results;
739 	}
740 	
741 	private List<ResultComponentInfo> assembleCreditOptions(
742 			List<CluResultInfo> cluResults) throws AssemblyException {
743 		String courseResultType = CourseAssemblerConstants.COURSE_RESULT_TYPE_CREDITS;
744 		List<ResultComponentInfo> results = new ArrayList<ResultComponentInfo>();
745 		//Loop through all the CluResults to find the one with the matching type
746 		for(CluResultInfo cluResult:cluResults){
747 			if(courseResultType.equals(cluResult.getType())){
748 				//Loop through all options and add to the list of Strings
749 				for(ResultOptionInfo resultOption: cluResult.getResultOptions()){
750 					try {
751 						ResultComponentInfo resultComponent = lrcService.getResultComponent(resultOption.getResultComponentId());
752 						results.add(resultComponent);
753 					} catch (DoesNotExistException e) {
754 						LOG.warn("Course Credit option:"+resultOption.getId()+" refers to non-existant ResultComponentInfo "+resultOption.getResultComponentId());
755 					} catch (Exception e) {
756 						throw new AssemblyException("Error getting result components",e);
757 					}
758 				}
759 			}
760 		}
761 		return results;
762 	}
763 	
764 	// TODO Use CluAssemblerUtils
765 	private List<BaseDTOAssemblyNode<?, ?>> disassembleLos(String cluId,
766 			CourseInfo course, NodeOperation operation) throws AssemblyException {
767 		// TODO Auto-generated method stub
768 		List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
769 
770 		// Get the current formats and put them in a map of format id/relation
771 		// id
772 		Map<String, CluLoRelationInfo> currentCluLoRelations = new HashMap<String, CluLoRelationInfo>();
773 		try {
774 			List<CluLoRelationInfo> cluLoRelations = luService.getCluLoRelationsByClu(cluId);
775 			for(CluLoRelationInfo cluLoRelation:cluLoRelations){
776 				if(CourseAssemblerConstants.COURSE_LO_COURSE_SPECIFIC_RELATION.equals(cluLoRelation.getType())){
777 					currentCluLoRelations.put(cluLoRelation.getLoId(), cluLoRelation);
778 				}
779 			}
780 		} catch (DoesNotExistException e) {
781 		} catch (Exception e) {
782 			throw new AssemblyException("Error finding related Los");
783 		}
784 		
785 		// Loop through all the los in this clu
786 		for(LoDisplayInfo loDisplay : course.getCourseSpecificLOs()){
787 
788 			// If this is a clu create/new lo update then all los will be created
789 		    if (NodeOperation.CREATE == operation
790 		            || (NodeOperation.UPDATE == operation &&  !currentCluLoRelations.containsKey(loDisplay.getLoInfo().getId()))) {
791 		        
792                 // the lo does not exist, so create
793                 // Assemble and add the lo
794 		    	loDisplay.getLoInfo().setId(null);
795 		    	loDisplay.getLoInfo().setState(course.getState());
796                 BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
797                         .disassemble(loDisplay, NodeOperation.CREATE);
798                 results.add(loNode);
799 
800                 // Create the relationship and add it as well
801                 CluLoRelationInfo relation = new CluLoRelationInfo();
802                 relation.setCluId(cluId);
803                 relation.setLoId(loNode.getNodeData().getId());
804                 relation
805                         .setType(CourseAssemblerConstants.COURSE_LO_COURSE_SPECIFIC_RELATION);
806                 relation.setState(course.getState());
807 
808                 BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo> relationNode = new BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo>(
809                         null);
810                 relationNode.setNodeData(relation);
811                 relationNode.setOperation(NodeOperation.CREATE);
812 
813                 results.add(relationNode);
814             } else if (NodeOperation.UPDATE == operation
815 					&& currentCluLoRelations.containsKey(loDisplay.getLoInfo().getId())) {
816 				// If the clu already has this lo, then just update the lo
817             	loDisplay.getLoInfo().setState(course.getState());
818                 BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
819                 		.disassemble(loDisplay, NodeOperation.UPDATE);
820 				results.add(loNode);
821 
822 				// remove this entry from the map so we can tell what needs to
823 				// be deleted at the end
824 				currentCluLoRelations.remove(loDisplay.getLoInfo().getId());
825 			} else if (NodeOperation.DELETE == operation
826                     && currentCluLoRelations.containsKey(loDisplay.getLoInfo().getId())) {
827 			    
828                 // Delete the Format and its relation
829 				CluLoRelationInfo relationToDelete = currentCluLoRelations.get(loDisplay.getLoInfo().getId());
830                 BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo>(
831                         null);
832                 relationToDeleteNode.setNodeData(relationToDelete);
833                 relationToDeleteNode.setOperation(NodeOperation.DELETE);
834                 results.add(relationToDeleteNode);
835             
836                 BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
837         				.disassemble(loDisplay, NodeOperation.DELETE);
838                 results.add(loNode);                                
839 
840                 // remove this entry from the map so we can tell what needs to
841                 // be deleted at the end
842                 currentCluLoRelations.remove(loDisplay.getLoInfo().getId());			    
843 			}
844 		}         
845 
846         // Now any leftover lo ids are no longer needed, so delete
847         // los and relations
848         for (Entry<String, CluLoRelationInfo> entry : currentCluLoRelations.entrySet()) {
849             // Create a new relation with the id of the relation we want to
850             // delete
851         	CluLoRelationInfo relationToDelete = entry.getValue();
852             BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<LoDisplayInfo, CluLoRelationInfo>(
853                     null);
854             relationToDeleteNode.setNodeData(relationToDelete);
855             relationToDeleteNode.setOperation(NodeOperation.DELETE);
856             results.add(relationToDeleteNode);
857 
858             try{
859             	LoInfo loToDelete = loService.getLo(entry.getKey());
860             
861 	            LoDisplayInfo loDisplayToDelete = loAssembler.assemble(loToDelete, null, false);
862 	            BaseDTOAssemblyNode<LoDisplayInfo, LoInfo> loNode = loAssembler
863 	            		.disassemble(loDisplayToDelete, NodeOperation.DELETE);
864 	            results.add(loNode);
865 	        } catch (DoesNotExistException e){
866 	        	LOG.warn("Trying to delete non exsistant LO:"+entry.getKey());
867             } catch (Exception e) {
868 				throw new AssemblyException("Error disassembling LOs",e);
869 			}
870         }
871 		
872 		return results;
873 	}
874 	
875 	private BaseDTOAssemblyNode<?, ?> disassembleGradingOptions(String cluId,
876 			String courseState, List<String> options, List<CluResultInfo> currentCluResults, NodeOperation operation) throws AssemblyException {
877 		BaseDTOAssemblyNode<List<String>, CluResultInfo> cluResultNode = new BaseDTOAssemblyNode<List<String>, CluResultInfo>(null);
878 				
879 		String courseResultType=CourseAssemblerConstants.COURSE_RESULT_TYPE_GRADE; 
880 		String resultsDescription="Grading options";
881 		String resultDescription="Grading option";
882 
883 		// Get the current options and put them in a map of option type id/cluResult
884 		Map<String, ResultOptionInfo> currentResults = new HashMap<String, ResultOptionInfo>();
885 
886 		CluResultInfo cluResult = null;
887 		
888 		//If this is not a create, lookup the results for this clu
889 		if (!NodeOperation.CREATE.equals(operation)) {
890 			for (CluResultInfo currentResult : currentCluResults) {
891 				if (courseResultType.equals(currentResult.getType())) {
892 					cluResult = currentResult;
893 					if(NodeOperation.DELETE.equals(operation)){
894 						//if this is a delete, then we only need the CluResultInfo
895 						cluResultNode.setOperation(NodeOperation.DELETE);
896 					}else{
897 						//Find all the Result options and store in a map for easy access later
898 						cluResultNode.setOperation(NodeOperation.UPDATE);
899 						for(ResultOptionInfo resultOption:currentResult.getResultOptions()){
900 							currentResults.put(resultOption.getResultComponentId(), resultOption);
901 						}
902 					}
903 					break;
904 				}
905 			}
906 		}
907 
908 		//If this is a delete we don't need the result options, just the CluResultInfo
909 		if(!NodeOperation.DELETE.equals(operation)){
910 			if(cluResult == null){
911 				//Create a new resultInfo of the given type if one does not exist and set operation to Create
912 				cluResult = new CluResultInfo();
913 				cluResult.setCluId(cluId);
914 				cluResult.setState(courseState);
915 				cluResult.setType(courseResultType);
916 				RichTextInfo desc = new RichTextInfo();
917 				desc.setPlain(resultsDescription);
918 				cluResult.setDesc(desc);
919 				cluResult.setEffectiveDate(new Date());
920 				cluResultNode.setOperation(NodeOperation.CREATE);
921 			}
922 	
923 			cluResult.setResultOptions(new ArrayList<ResultOptionInfo>());
924 	
925 			// Loop through all the credit options in this course
926 			for (String optionType : options) {
927 				if(currentResults.containsKey(optionType)){
928 					//If the option exists already copy it to the new list of result options
929 					ResultOptionInfo resultOptionInfo = currentResults.get(optionType);
930 					cluResult.getResultOptions().add(resultOptionInfo);
931 				}else{
932 					//Otherwise create a new result option
933 					ResultOptionInfo resultOptionInfo = new ResultOptionInfo();
934 					RichTextInfo desc = new RichTextInfo();
935 					desc.setPlain(resultDescription);
936 					resultOptionInfo.setDesc(desc);
937 					resultOptionInfo.setResultComponentId(optionType);
938 					resultOptionInfo.setState(courseState);
939 					
940 					cluResult.getResultOptions().add(resultOptionInfo);
941 				}
942 			}
943 		}
944 		
945 		cluResultNode.setNodeData(cluResult);
946 		return cluResultNode;
947 	}
948 
949 	// TODO This is pretty much a copy of the FormatAssembler's
950 	// disassembleActivities code... maybe can be made generic
951 	private List<BaseDTOAssemblyNode<?, ?>> disassembleFormats(String nodeId,
952 			CourseInfo course, NodeOperation operation)
953 			throws AssemblyException, DoesNotExistException, InvalidParameterException, MissingParameterException, OperationFailedException {
954 
955 		List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
956 
957 		// Get the current formats and put them in a map of format id/relation
958 		// id
959 		Map<String, String> currentformatIds = new HashMap<String, String>();
960 
961 		if (!NodeOperation.CREATE.equals(operation)) {
962 			try {
963 				List<CluCluRelationInfo> formatRelationships = luService
964 						.getCluCluRelationsByClu(course.getId());
965 				
966 				//formatRelationships = (null == formatRelationships) ? new ArrayList<CluCluRelationInfo>() : formatRelationships;
967 				
968 				for (CluCluRelationInfo formatRelation : formatRelationships) {
969 					if (CourseAssemblerConstants.COURSE_FORMAT_RELATION_TYPE
970 							.equals(formatRelation.getType())) {
971 						currentformatIds.put(formatRelation.getRelatedCluId(),
972 								formatRelation.getId());
973 					}
974 				}
975 			} catch (DoesNotExistException e) {
976 			} catch (InvalidParameterException e) {
977 				throw new AssemblyException("Error getting related formats", e);
978 			} catch (MissingParameterException e) {
979 				throw new AssemblyException("Error getting related formats", e);
980 			} catch (OperationFailedException e) {
981 				throw new AssemblyException("Error getting related formats", e);
982 			}
983 		}
984 
985 		// Loop through all the formats in this course
986 		for (FormatInfo format : course.getFormats()) {
987 
988 		    //  If this is a course create/new format update then all formats will be created
989 		    if (NodeOperation.CREATE == operation
990 		            || (NodeOperation.UPDATE == operation && !currentformatIds.containsKey(format.getId()) )) {
991                 // the format does not exist, so create
992                 // Assemble and add the format
993 		    	format.setState(course.getState());
994                 BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
995                         .disassemble(format, NodeOperation.CREATE);
996                 formatNode.getNodeData().setState(course.getState());
997                 results.add(formatNode);
998 
999                 
1000                 // Create the relationship and add it as well
1001                 CluCluRelationInfo relation = new CluCluRelationInfo();
1002                 relation.setCluId(nodeId);
1003                 relation.setRelatedCluId(formatNode.getNodeData().getId());// this
1004                 // should
1005                 // already
1006                 // be set even if
1007                 // it's a create
1008                 relation
1009                         .setType(CourseAssemblerConstants.COURSE_FORMAT_RELATION_TYPE);
1010                 relation.setState(course.getState());
1011 
1012                 BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo> relationNode = new BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo>(
1013                         null);
1014                 relationNode.setNodeData(relation);
1015                 relationNode.setOperation(NodeOperation.CREATE);
1016 
1017                 results.add(relationNode);
1018             } else if (NodeOperation.UPDATE == operation
1019 					&& currentformatIds.containsKey(format.getId())) {
1020 				// If the course already has this format, then just update the
1021 				// format
1022             	format.setState(course.getState());
1023 				BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
1024 						.disassemble(format, NodeOperation.UPDATE);
1025 				formatNode.getNodeData().setState(course.getState());
1026 				results.add(formatNode);
1027 
1028 				// remove this entry from the map so we can tell what needs to
1029 				// be deleted at the end
1030 				currentformatIds.remove(format.getId());
1031 			} else if (NodeOperation.DELETE == operation
1032                     && currentformatIds.containsKey(format.getId()))  {
1033 			    // Delete the Format and its relation
1034 	            CluCluRelationInfo relationToDelete = new CluCluRelationInfo();
1035 	            relationToDelete.setId( currentformatIds.get(format.getId()) );
1036 	            BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo>(
1037 	                    null);
1038 	            relationToDeleteNode.setNodeData(relationToDelete);
1039 	            relationToDeleteNode.setOperation(NodeOperation.DELETE);
1040 	            results.add(relationToDeleteNode);
1041 			
1042                 BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
1043                 .disassemble(format, NodeOperation.DELETE);
1044                 results.add(formatNode);	            	            
1045 
1046                 // remove this entry from the map so we can tell what needs to
1047                 // be deleted at the end
1048                 currentformatIds.remove(format.getId());
1049 			}			    
1050 		}
1051 		
1052         // Now any leftover format ids are no longer needed, so delete
1053         // formats and relations. These formats have to be assembled first before they can be marked for deletion
1054         for (Entry<String, String> entry : currentformatIds.entrySet()) {
1055             // Create a new relation with the id of the relation we want to
1056             // delete
1057             CluCluRelationInfo relationToDelete = new CluCluRelationInfo();
1058             relationToDelete.setId( entry.getValue() );
1059             BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<CourseInfo, CluCluRelationInfo>(
1060                     null);
1061             relationToDeleteNode.setNodeData(relationToDelete);
1062             relationToDeleteNode.setOperation(NodeOperation.DELETE);
1063             results.add(relationToDeleteNode);
1064             
1065             CluInfo formatCluToDelete = luService.getClu(entry.getKey());
1066             FormatInfo formatToDelete = formatAssembler.assemble(formatCluToDelete, null, false);
1067             BaseDTOAssemblyNode<FormatInfo, CluInfo> formatNode = formatAssembler
1068             .disassemble(formatToDelete, NodeOperation.DELETE);
1069             results.add(formatNode);                                            
1070         }
1071 
1072 		return results;
1073 	}
1074 	
1075 	private List<CourseVariationInfo> assembleVariations(List<CluIdentifierInfo> cluIdents) {
1076 		List<CourseVariationInfo> variations = new ArrayList<CourseVariationInfo>();
1077 		if (cluIdents != null) {
1078 			for (CluIdentifierInfo cluIdent : cluIdents) {
1079 				if (cluIdent.getType() != null && 
1080 						cluIdent.getType().equals(CourseAssemblerConstants.COURSE_VARIATION_IDENT_TYPE)) {
1081 					CourseVariationInfo variation = new CourseVariationInfo();
1082 					variation.setId(cluIdent.getId());
1083 					variation.setType(cluIdent.getType());
1084 					variation.setCourseNumberSuffix(cluIdent.getSuffixCode());
1085 					variation.setSubjectArea(cluIdent.getDivision());
1086 					variation.setVariationCode(cluIdent.getVariation());
1087 					variation.setVariationTitle(cluIdent.getLongName());
1088 					variations.add(variation);
1089 				}
1090 			}
1091 		}
1092 		return variations;
1093 	}
1094 	
1095 	private List<CourseCrossListingInfo> assembleCrossListings(List<CluIdentifierInfo> cluIdents) {
1096 		List<CourseCrossListingInfo> crossListings = new ArrayList<CourseCrossListingInfo>();
1097 		if (cluIdents != null) {
1098 			for (CluIdentifierInfo cluIdent : cluIdents) {
1099 				if (cluIdent.getType() != null && 
1100 						cluIdent.getType().equals(CourseAssemblerConstants.COURSE_CROSSLISTING_IDENT_TYPE)) {
1101 					CourseCrossListingInfo crosslisting = new CourseCrossListingInfo();
1102 					crosslisting.setId(cluIdent.getId());
1103 					crosslisting.setCode(cluIdent.getCode());
1104 					crosslisting.setAttributes(cluIdent.getAttributes());
1105 					crosslisting.setType(cluIdent.getType());
1106 					crosslisting.setCourseNumberSuffix(cluIdent.getSuffixCode());
1107 					crosslisting.setSubjectArea(cluIdent.getDivision());
1108 					crosslisting.setDepartment(cluIdent.getOrgId());
1109 					crossListings.add(crosslisting);
1110 				}
1111 			}
1112 		}
1113 		return crossListings;
1114 	}
1115 	
1116 	// TODO This is pretty much a copy of the disassembleJoints
1117 	// code... maybe can be made generic
1118 	private List<BaseDTOAssemblyNode<?, ?>> disassembleJoints(String nodeId,
1119 			CourseInfo course, NodeOperation operation)
1120 			throws AssemblyException {
1121 
1122 		List<BaseDTOAssemblyNode<?, ?>> results = new ArrayList<BaseDTOAssemblyNode<?, ?>>();
1123 
1124 		// Get the current joints and put them in a map of joint id/relation
1125 		// id
1126 		Map<String, CluCluRelationInfo> currentJointIds = new HashMap<String, CluCluRelationInfo>();
1127 
1128 		if (!NodeOperation.CREATE.equals(operation)) {
1129 			try {
1130 				List<CluCluRelationInfo> jointRelationships = luService
1131 						.getCluCluRelationsByClu(course.getId());
1132 				for (CluCluRelationInfo jointRelation : jointRelationships) {
1133 					if (CourseAssemblerConstants.JOINT_RELATION_TYPE
1134 							.equals(jointRelation.getType())) {
1135 						currentJointIds.put(jointRelation.getId(),jointRelation);
1136 					}
1137 				}
1138 			} catch (DoesNotExistException e) {
1139 			} catch (InvalidParameterException e) {
1140 				throw new AssemblyException("Error getting related formats", e);
1141 			} catch (MissingParameterException e) {
1142 				throw new AssemblyException("Error getting related formats", e);
1143 			} catch (OperationFailedException e) {
1144 				throw new AssemblyException("Error getting related formats", e);
1145 			}
1146 		}
1147 
1148 		// Loop through all the joints in this course
1149 		for (CourseJointInfo joint : course.getJoints()) {
1150 
1151 			// If this is a course create then all joints will be created
1152 			if (NodeOperation.UPDATE.equals(operation) && joint.getRelationId() != null
1153 					&& currentJointIds.containsKey(joint.getRelationId())) {
1154 				// remove this entry from the map so we can tell what needs to
1155 				// be deleted at the end
1156 				CluCluRelationInfo relation = currentJointIds.remove(joint.getRelationId());
1157 				relation.setRelatedCluId(joint.getCourseId());
1158 				BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo> jointNode = new BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo>(courseJointAssembler);
1159 				jointNode.setBusinessDTORef(joint);
1160 				jointNode.setNodeData(relation);
1161 				jointNode.setOperation(NodeOperation.UPDATE);
1162 				jointNode.getNodeData().setState(course.getState());
1163 				results.add(jointNode);
1164 			} else if (!NodeOperation.DELETE.equals(operation)) {
1165 				// the joint does not exist, so create cluclurelation
1166 				BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo> jointNode = courseJointAssembler
1167 						.disassemble(joint, NodeOperation.CREATE);
1168 				jointNode.getNodeData().setCluId(nodeId);
1169 				jointNode.getNodeData().setState(course.getState());
1170 				results.add(jointNode);
1171 			}
1172 		}
1173 
1174         // Now any leftover joint ids are no longer needed, so delete
1175         // joint relations
1176         for (String id : currentJointIds.keySet()) {
1177             // Create a new relation with the id of the relation we want to
1178             // delete
1179             CluCluRelationInfo relationToDelete = new CluCluRelationInfo();
1180             relationToDelete.setId(id);
1181             BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo> relationToDeleteNode = new BaseDTOAssemblyNode<CourseJointInfo, CluCluRelationInfo>(
1182                     courseJointAssembler);
1183             relationToDeleteNode.setNodeData(relationToDelete);
1184             relationToDeleteNode.setOperation(NodeOperation.DELETE);
1185             results.add(relationToDeleteNode);
1186         }
1187 		
1188 		return results;
1189 	}
1190 
1191 	/**
1192 	 * 
1193 	 * This method calculates code for course and cross listed course.
1194 	 * 
1195 	 * @param subjectArea
1196 	 * @param suffixNumber
1197 	 * @return
1198 	 */
1199 	private String calculateCourseCode(String subjectArea, String suffixNumber) {
1200 	    return subjectArea + suffixNumber;
1201 	}
1202 	
1203 	public void setLuService(LuService luService) {
1204 		this.luService = luService;
1205 	}
1206 
1207 	public void setFormatAssembler(FormatAssembler formatAssembler) {
1208 		this.formatAssembler = formatAssembler;
1209 	}
1210 
1211 	public void setCourseJointAssembler(
1212 			CourseJointAssembler courseJointAssembler) {
1213 		this.courseJointAssembler = courseJointAssembler;
1214 	}
1215 
1216 	public void setLoAssembler(LoAssembler loAssembler) {
1217 		this.loAssembler = loAssembler;
1218 	}
1219 
1220 	public void setLoService(LearningObjectiveService loService) {
1221 		this.loService = loService;
1222 	}
1223 
1224     public void setCluAssemblerUtils(CluAssemblerUtils cluAssemblerUtils) {
1225         this.cluAssemblerUtils = cluAssemblerUtils;
1226     }
1227 
1228 	public void setLrcService(LrcService lrcService) {
1229 		this.lrcService = lrcService;
1230 	}
1231 }