001 /** 002 * Copyright 2010 The Kuali Foundation Licensed under the 003 * Educational Community License, Version 2.0 (the "License"); you may 004 * not use this file except in compliance with the License. You may 005 * obtain a copy of the License at 006 * 007 * http://www.osedu.org/licenses/ECL-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, 010 * software distributed under the License is distributed on an "AS IS" 011 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 012 * or implied. See the License for the specific language governing 013 * permissions and limitations under the License. 014 */ 015 package org.kuali.student.lum.program.client.requirements; 016 017 import java.util.*; 018 019 import org.kuali.student.r1.common.assembly.data.Data; 020 import org.kuali.student.r2.common.util.ContextUtils; 021 import org.kuali.student.r1.core.statement.dto.StatementTreeViewInfo; 022 import org.kuali.student.r1.core.statement.dto.StatementTypeInfo; 023 import org.kuali.student.common.ui.client.application.KSAsyncCallback; 024 import org.kuali.student.common.ui.client.mvc.*; 025 import org.kuali.student.core.statement.ui.client.widgets.rules.RulesUtil; 026 import org.kuali.student.lum.program.client.ProgramConstants; 027 import org.kuali.student.lum.program.client.events.StoreRequirementIDsEvent; 028 import org.kuali.student.lum.program.client.events.StoreSpecRequirementIDsEvent; 029 import org.kuali.student.lum.program.client.rpc.MajorDisciplineRpcService; 030 import org.kuali.student.lum.program.client.rpc.MajorDisciplineRpcServiceAsync; 031 import org.kuali.student.lum.program.client.rpc.StatementRpcService; 032 import org.kuali.student.lum.program.client.rpc.StatementRpcServiceAsync; 033 import org.kuali.student.r2.lum.program.dto.ProgramRequirementInfo; 034 035 import com.google.gwt.core.client.GWT; 036 import com.google.gwt.event.shared.HandlerManager; 037 import com.google.gwt.user.client.Window; 038 039 public class ProgramRequirementsDataModel { 040 041 private final MajorDisciplineRpcServiceAsync programRemoteService = GWT.create(MajorDisciplineRpcService.class); 042 private StatementRpcServiceAsync statementRpcServiceAsync = GWT.create(StatementRpcService.class); 043 private Model model = null; 044 private HandlerManager eventBus; 045 046 //keeping track of rules and rule state 047 public enum requirementState {STORED, ADDED, EDITED, DELETED;} 048 private Map<Integer, ProgramRequirementInfo> progReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>(); 049 private Map<Integer, ProgramRequirementInfo> origProgReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>(); 050 private Map<Integer, requirementState> progReqState = new HashMap<Integer, requirementState>(); 051 private Map<Integer, requirementState> origProgReqState = new HashMap<Integer, requirementState>(); 052 private List<StatementTypeInfo> stmtTypes = new ArrayList<StatementTypeInfo>(); 053 private boolean isInitialized = false; 054 private static Integer progReqIDs = 111111; 055 056 public ProgramRequirementsDataModel(HandlerManager eventBus) { 057 this.eventBus = eventBus; 058 } 059 060 //find out whether we need to reset rules based on whether we have a new program ID or not 061 public void setupRules(Controller parentController, String modelId, final Callback<Boolean> onReadyCallback) { 062 parentController.requestModel(modelId, new ModelRequestCallback() { 063 064 @Override 065 public void onRequestFail(Throwable cause) { 066 GWT.log("Unable to retrieve program model for program summary view", cause); 067 onReadyCallback.exec(false); 068 } 069 070 @Override 071 public void onModelReady(Model modelIn) { 072 //TODO how can we reliably know that we need to reload rules (or not) 073 //String programId = (model == null ? null : (String)((DataModel)model).getRoot().get("id")); 074 //String modelProgramId = ((DataModel)modelIn).getRoot().get(ProgramConstants.ID); 075 //if ((modelProgramId == null) || (!modelProgramId.equals(programId))) { 076 resetRules(); 077 //} 078 model = modelIn; 079 onReadyCallback.exec(true); 080 } 081 }); 082 } 083 084 private void resetRules() { 085 progReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>(); 086 origProgReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>(); 087 progReqState = new HashMap<Integer, requirementState>(); 088 origProgReqState = new HashMap<Integer, requirementState>(); 089 stmtTypes = new ArrayList<StatementTypeInfo>(); 090 model = null; 091 isInitialized = false; 092 } 093 094 //retrieve rules based on IDs stored in this program 095 public void retrieveProgramRequirements(Controller parentController, String modelId, final Callback<Boolean> onReadyCallback) { 096 097 setupRules(parentController, modelId, new Callback<Boolean>() { 098 @Override 099 public void exec(Boolean result) { 100 Data program = ((DataModel)model).getRoot().get(ProgramConstants.PROGRAM_REQUIREMENTS); 101 102 Iterator<Data.Property> realPropertyIterator = program.realPropertyIterator(); 103 ArrayList<String> programRequirementIds = new ArrayList<String>(); 104 while(realPropertyIterator.hasNext()) { 105 programRequirementIds.add((String)realPropertyIterator.next().getValue()); 106 } 107 retrieveStatementTypes(programRequirementIds, onReadyCallback); 108 } 109 }); 110 } 111 112 private void retrieveStatementTypes(final List<String> programRequirementIds, final Callback<Boolean> onReadyCallback) { 113 114 //retrieve available program requirement types 115 statementRpcServiceAsync.getStatementTypesForStatementType("kuali.statement.type.program", new KSAsyncCallback<List<StatementTypeInfo>>() { 116 @Override 117 public void handleFailure(Throwable caught) { 118 Window.alert(caught.getMessage()); 119 GWT.log("getStatementTypes failed", caught); 120 onReadyCallback.exec(false); 121 } 122 123 @Override 124 public void onSuccess(List<StatementTypeInfo> stmtInfoTypes) { 125 //store the statement types 126 for (StatementTypeInfo stmtInfoType : stmtInfoTypes) { 127 stmtTypes.add(stmtInfoType); 128 } 129 130 //now retrieve the actual rules 131 retrieveRules(programRequirementIds, onReadyCallback); 132 } 133 }); 134 } 135 136 private void retrieveRules(List<String> programRequirementIds, final Callback<Boolean> onReadyCallback) { 137 138 //true if no program requirements exist yet 139 if ((programRequirementIds == null) || programRequirementIds.isEmpty()) { 140 isInitialized = true; 141 onReadyCallback.exec(true); 142 return; 143 } 144 145 programRemoteService.getProgramRequirements(programRequirementIds, new KSAsyncCallback<List<ProgramRequirementInfo>>() { 146 @Override 147 public void handleFailure(Throwable caught) { 148 Window.alert(caught.getMessage()); 149 GWT.log("getProgramRequirement failed", caught); 150 onReadyCallback.exec(false); 151 } 152 153 @Override 154 public void onSuccess(List<ProgramRequirementInfo> programReqInfos) { 155 //update rules list with new program requirements 156 origProgReqInfos.clear(); 157 origProgReqState.clear(); 158 progReqInfos.clear(); 159 progReqState.clear(); 160 for (ProgramRequirementInfo programReqInfo : programReqInfos) { 161 162 if (getStmtTypeInfo(programReqInfo.getStatement().getType()) == null) { 163 Window.alert("Did not find corresponding statement type for program requirement of type: " + programReqInfo.getStatement().getType()); 164 GWT.log("Did not find corresponding statement type for program requirement of type: " + programReqInfo.getStatement().getType(), null); 165 } 166 167 setRuleState(programReqInfo); 168 origProgReqInfos.put(progReqIDs, cloneProgReq(programReqInfo)); 169 origProgReqState.put(progReqIDs, requirementState.STORED); 170 progReqInfos.put(progReqIDs, programReqInfo); 171 progReqState.put(progReqIDs++, requirementState.STORED); 172 } 173 174 isInitialized = true; 175 onReadyCallback.exec(true); 176 } 177 }); 178 } 179 180 181 182 public ProgramRequirementInfo updateRules(StatementTreeViewInfo newSubRule, Integer internalProgReqID, boolean isNewRule) { 183 184 ProgramRequirementInfo affectedRule = progReqInfos.get(internalProgReqID); 185 186 if (affectedRule == null) { 187 Window.alert("Cannot find program requirement with a statement that has id: '" + newSubRule.getId() + "'"); 188 GWT.log("Cannot find program requirement with a statement that has id: '" + newSubRule.getId() + "'", null); 189 return null; 190 } 191 192 if (progReqState.get(internalProgReqID) == ProgramRequirementsDataModel.requirementState.STORED) { 193 progReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.EDITED); 194 } 195 196 //if we don't have top level req. components wrapped in statement, do so before we add another statement 197 StatementTreeViewInfo affectedTopTree = affectedRule.getStatement(); 198 if ((affectedTopTree.getReqComponents() != null) && !affectedTopTree.getReqComponents().isEmpty()) { 199 StatementTreeViewInfo stmtTree = new StatementTreeViewInfo(); 200 stmtTree.setId(ProgramRequirementsSummaryView.generateStatementTreeId()); 201 stmtTree.setType( affectedRule.getStatement().getType()); 202 stmtTree.setReqComponents(affectedTopTree.getReqComponents()); 203 List<StatementTreeViewInfo> stmtList = new ArrayList<StatementTreeViewInfo>(); 204 stmtList.add(stmtTree); 205 affectedTopTree.setStatements(stmtList); 206 } 207 //now update the actual rule 208 List<StatementTreeViewInfo> affectedSubRules = affectedTopTree.getStatements(); 209 if (isNewRule) { 210 affectedSubRules.add(newSubRule); 211 } else { 212 //update rule 213 if (affectedSubRules == null || affectedSubRules.isEmpty()) { 214 affectedRule.setStatement(newSubRule); 215 } else { //replace the related stored subrule with a new version 216 for (StatementTreeViewInfo subRule : affectedSubRules) { 217 if (subRule.getId().equals(newSubRule.getId())) { 218 int treeIx = affectedSubRules.indexOf(subRule); 219 //only update if the rule is not empty 220 if (!isEmptyRule(newSubRule)) { 221 affectedSubRules.add(treeIx, newSubRule); 222 } 223 affectedSubRules.remove(subRule); 224 break; 225 } 226 } 227 } 228 } 229 230 return affectedRule; 231 } 232 233 public void updateProgramEntities(final Callback<List<ProgramRequirementInfo>> callback) { 234 235 final List<String> referencedProgReqIds = new ArrayList<String>(); 236 237 programRemoteService.storeProgramRequirements(progReqState, progReqInfos, new KSAsyncCallback<Map<Integer, ProgramRequirementInfo>>() { 238 @Override 239 public void handleFailure(Throwable caught) { 240 Window.alert(caught.getMessage()); 241 GWT.log("storeProgramRequirements failed", caught); 242 } 243 @Override 244 public void onSuccess(Map<Integer, ProgramRequirementInfo> storedRules) { 245 246 for (Integer internalProgReqID : storedRules.keySet()) { 247 ProgramRequirementInfo storedRule = storedRules.get(internalProgReqID); 248 switch (progReqState.get(internalProgReqID)) { 249 case STORED: 250 //rule was not changed so continue 251 referencedProgReqIds.add(progReqInfos.get(internalProgReqID).getId()); 252 break; 253 case ADDED: 254 referencedProgReqIds.add(storedRule.getId()); 255 progReqInfos.put(internalProgReqID, storedRule); 256 origProgReqInfos.put(internalProgReqID, cloneProgReq(storedRule)); 257 origProgReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.STORED); 258 progReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.STORED); 259 break; 260 case EDITED: 261 referencedProgReqIds.add(storedRule.getId()); 262 progReqInfos.put(internalProgReqID, storedRule); 263 origProgReqInfos.put(internalProgReqID, cloneProgReq(storedRule)); 264 origProgReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.STORED); 265 progReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.STORED); 266 break; 267 case DELETED: 268 progReqInfos.remove(internalProgReqID); 269 origProgReqInfos.remove(internalProgReqID); 270 origProgReqState.remove(internalProgReqID); 271 progReqState.remove(internalProgReqID); 272 break; 273 default: 274 break; 275 } 276 } 277 278 saveRequirementIds(referencedProgReqIds, storedRules, callback); 279 } 280 }); 281 } 282 283 private void saveRequirementIds(final List<String> referencedProgReqIds, final Map<Integer, ProgramRequirementInfo> storedRules, final Callback<List<ProgramRequirementInfo>> callback) { 284 String programId = ((DataModel)model).getRoot().get("id"); 285 String programType = ((DataModel)model).getRoot().get("type"); 286 287 //for some reason, credential program has type stored in 'credentialProgramType' 288 if (programType == null) { 289 programType = ((DataModel)model).getRoot().get("credentialProgramType"); 290 } 291 292 //specializations will be handled differently from Major 293 if (programType.equals(ProgramConstants.VARIATION_TYPE_KEY)) { 294 eventBus.fireEvent(new StoreSpecRequirementIDsEvent(programId, programType, referencedProgReqIds)); 295 } else { 296 eventBus.fireEvent(new StoreRequirementIDsEvent(programId, programType, referencedProgReqIds)); 297 } 298 299 callback.exec(new ArrayList(storedRules.values())); //update display widgets 300 } 301 302 303 public List<ProgramRequirementInfo> getProgReqInfo(String stmtTypeId) { 304 List<ProgramRequirementInfo> rules = new ArrayList<ProgramRequirementInfo>(); 305 for(ProgramRequirementInfo progReqInfo : progReqInfos.values()) { 306 if (progReqInfo.getStatement().getType().equals(stmtTypeId)) { 307 rules.add(progReqInfo); 308 } 309 } 310 return rules; 311 } 312 313 public Integer getInternalProgReqID(ProgramRequirementInfo progReqInfo) { 314 for(Integer key : progReqInfos.keySet()) { 315 if (progReqInfos.get(key) == progReqInfo) { 316 return key; 317 } 318 } 319 320 Window.alert("Problem retrieving key for program requirement: " + progReqInfo.getId()); 321 GWT.log("Problem retrieving key for program requirement: " + progReqInfo.getId(), null); 322 323 return null; 324 } 325 326 public StatementTypeInfo getStmtTypeInfo(String stmtTypeId) { 327 for (StatementTypeInfo stmtInfo : stmtTypes) { 328 if (stmtInfo.getId().equals(stmtTypeId)) { 329 return stmtInfo; 330 } 331 } 332 333 Window.alert("Did not find StatementTypeInfo based on type: " + stmtTypeId); 334 GWT.log("Did not find StatementTypeInfo based on type: " + stmtTypeId); 335 336 return null; 337 } 338 339 public void deleteRule(Integer internalProgReqID) { 340 if (progReqState.get(internalProgReqID) == ProgramRequirementsDataModel.requirementState.ADDED) { 341 //user added a rule, didn't save it and now wants to delete it 342 progReqState.remove(internalProgReqID); 343 progReqInfos.remove(internalProgReqID); 344 } else { 345 markRuleAsDeleted(internalProgReqID); 346 } 347 } 348 349 public void addRule(ProgramRequirementInfo programReqInfo) { 350 setRuleState(programReqInfo); 351 progReqInfos.put(progReqIDs, programReqInfo); 352 progReqState.put(progReqIDs++, requirementState.ADDED); 353 } 354 355 public void updateRule(Integer internalProgReqID, ProgramRequirementInfo programReqInfo) { 356 setRuleState(programReqInfo); 357 progReqInfos.put(internalProgReqID, programReqInfo); 358 markRuleAsEdited(internalProgReqID); 359 } 360 361 /** 362 * Set the state of the program requirement to state of the program. 363 * 364 * @param programReqInfo 365 */ 366 protected void setRuleState(ProgramRequirementInfo programReqInfo) { 367 if (model != null) { 368 String programState = ((DataModel) model).get(ProgramConstants.STATE); 369 programReqInfo.setStateKey(programState); 370 } 371 } 372 373 public void markRuleAsDeleted(Integer internalProgReqID) { 374 if ((progReqState.get(internalProgReqID) == ProgramRequirementsDataModel.requirementState.STORED) || 375 (progReqState.get(internalProgReqID) == ProgramRequirementsDataModel.requirementState.EDITED)) { 376 progReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.DELETED); 377 } 378 } 379 380 public void markRuleAsEdited(Integer internalProgReqID) { 381 if (progReqState.get(internalProgReqID) == ProgramRequirementsDataModel.requirementState.STORED) { 382 progReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.EDITED); 383 } 384 } 385 386 public String getStmtTypeName(String stmtTypeId) { 387 String name = getStmtTypeInfo(stmtTypeId).getName(); 388 return (name == null ? "" : name); 389 } 390 391 public boolean isRuleExists(String stmtTypeId) { 392 boolean showNoRuleText = true; 393 for(ProgramRequirementInfo ruleInfo : progReqInfos.values()) { 394 if ((ruleInfo.getStatement().getType().equals(stmtTypeId)) && (progReqState.get(getInternalProgReqID(ruleInfo)) != ProgramRequirementsDataModel.requirementState.DELETED)) { 395 showNoRuleText = false; 396 } 397 } 398 return showNoRuleText; 399 } 400 401 public boolean isDirty() { 402 403 if (progReqState.size() != origProgReqState.size()) { 404 return true; 405 } 406 407 for(Integer key : progReqState.keySet()) { 408 if (!progReqState.get(key).equals(origProgReqState.get(key))) { 409 return true; 410 } 411 } 412 413 /* 414 for(Integer key : progReqInfos.keySet()) { 415 if (!progReqInfos.get(key).equals(origProgReqInfos.get(key))) { 416 return true; 417 } 418 } */ 419 return false; 420 } 421 422 public void revertRuleChanges() { 423 progReqInfos = new HashMap<Integer, ProgramRequirementInfo>(); 424 progReqState = new HashMap<Integer, requirementState>(); 425 for(Integer key : origProgReqInfos.keySet()) { 426 progReqInfos.put(key, cloneProgReq(origProgReqInfos.get(key))); 427 progReqState.put(key, origProgReqState.get(key)); 428 } 429 } 430 431 public ProgramRequirementInfo getProgReqByInternalId(Integer internalProgReqID) { 432 return progReqInfos.get(internalProgReqID); 433 } 434 435 public boolean isEmptyRule(StatementTreeViewInfo tree) { 436 return (tree.getStatements() == null || tree.getStatements().isEmpty() && (tree.getReqComponents() == null || tree.getReqComponents().isEmpty())); 437 } 438 439 public boolean isInitialized() { 440 return isInitialized; 441 } 442 443 public void setInitialized(boolean initialized) { 444 isInitialized = initialized; 445 } 446 447 public List<StatementTypeInfo> getStmtTypes() { 448 return stmtTypes; 449 } 450 451 private ProgramRequirementInfo cloneProgReq(ProgramRequirementInfo inProgReqInfo) { 452 ProgramRequirementInfo clonedProgReqInfo = null; 453 if (inProgReqInfo != null) { 454 clonedProgReqInfo = new ProgramRequirementInfo(); 455 clonedProgReqInfo.setId(inProgReqInfo.getId()); 456 clonedProgReqInfo.setShortTitle(inProgReqInfo.getShortTitle()); 457 clonedProgReqInfo.setLongTitle(inProgReqInfo.getLongTitle()); 458 clonedProgReqInfo.setDescr(inProgReqInfo.getDescr()); 459 clonedProgReqInfo.setMinCredits(inProgReqInfo.getMinCredits()); 460 clonedProgReqInfo.setMaxCredits(inProgReqInfo.getMaxCredits()); 461 clonedProgReqInfo.setStateKey(inProgReqInfo.getStateKey()); 462 clonedProgReqInfo.setTypeKey(inProgReqInfo.getTypeKey()); 463 clonedProgReqInfo.setStatement(RulesUtil.clone(inProgReqInfo.getStatement())); 464 //TODO clonedProgReqInfo.setAttributes(); 465 //TODO clonedProgReqInfo.setLearningObjectives(); 466 //TODO clonedProgReqInfo.setMetaInfo(); 467 } 468 return clonedProgReqInfo; 469 } 470 }