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 }