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