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