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 }