View Javadoc

1   /**
2    * Copyright 2010 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   */
15  package org.kuali.student.lum.program.client.requirements;
16  
17  import java.util.*;
18  
19  import org.kuali.student.r1.common.assembly.data.Data;
20  import org.kuali.student.r2.common.util.ContextUtils;
21  import org.kuali.student.r1.core.statement.dto.StatementTreeViewInfo;
22  import org.kuali.student.r1.core.statement.dto.StatementTypeInfo;
23  import org.kuali.student.common.ui.client.application.KSAsyncCallback;
24  import org.kuali.student.common.ui.client.mvc.*;
25  import org.kuali.student.core.statement.ui.client.widgets.rules.RulesUtil;
26  import org.kuali.student.lum.program.client.ProgramConstants;
27  import org.kuali.student.lum.program.client.events.StoreRequirementIDsEvent;
28  import org.kuali.student.lum.program.client.events.StoreSpecRequirementIDsEvent;
29  import org.kuali.student.lum.program.client.rpc.MajorDisciplineRpcService;
30  import org.kuali.student.lum.program.client.rpc.MajorDisciplineRpcServiceAsync;
31  import org.kuali.student.lum.program.client.rpc.StatementRpcService;
32  import org.kuali.student.lum.program.client.rpc.StatementRpcServiceAsync;
33  import org.kuali.student.r2.lum.program.dto.ProgramRequirementInfo;
34  
35  import com.google.gwt.core.client.GWT;
36  import com.google.gwt.event.shared.HandlerManager;
37  import com.google.gwt.user.client.Window;
38  
39  public class ProgramRequirementsDataModel {
40  
41      private final MajorDisciplineRpcServiceAsync programRemoteService = GWT.create(MajorDisciplineRpcService.class);
42      private StatementRpcServiceAsync statementRpcServiceAsync = GWT.create(StatementRpcService.class);
43      private Model model = null;
44      private HandlerManager eventBus;
45  
46      //keeping track of rules and rule state
47      public enum requirementState {STORED, ADDED, EDITED, DELETED;}
48      private Map<Integer, ProgramRequirementInfo> progReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>();
49      private Map<Integer, ProgramRequirementInfo> origProgReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>();
50      private Map<Integer, requirementState> progReqState = new HashMap<Integer, requirementState>();
51      private Map<Integer, requirementState> origProgReqState = new HashMap<Integer, requirementState>();
52      private List<StatementTypeInfo> stmtTypes = new ArrayList<StatementTypeInfo>();
53      private boolean isInitialized = false;
54      private static Integer progReqIDs = 111111;
55  
56      public ProgramRequirementsDataModel(HandlerManager eventBus) {
57          this.eventBus = eventBus;
58      }
59  
60      //find out whether we need to reset rules based on whether we have a new program ID or not
61      public void setupRules(Controller parentController, String modelId, final Callback<Boolean> onReadyCallback) {
62          parentController.requestModel(modelId, new ModelRequestCallback() {
63  
64              @Override
65              public void onRequestFail(Throwable cause) {
66                  GWT.log("Unable to retrieve program model for program summary view", cause);
67                  onReadyCallback.exec(false);
68              }
69  
70              @Override
71              public void onModelReady(Model modelIn) {
72                  //TODO how can we reliably know that we need to reload rules (or not)
73                  //String programId = (model == null ? null : (String)((DataModel)model).getRoot().get("id"));
74                  //String modelProgramId = ((DataModel)modelIn).getRoot().get(ProgramConstants.ID);
75                  //if ((modelProgramId == null) || (!modelProgramId.equals(programId))) {
76                      resetRules();
77                  //}
78                  model = modelIn;
79                  onReadyCallback.exec(true);
80              }
81          });
82      }
83  
84      private void resetRules() {
85          progReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>();
86          origProgReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>();
87          progReqState = new HashMap<Integer, requirementState>();
88          origProgReqState = new HashMap<Integer, requirementState>();
89          stmtTypes = new ArrayList<StatementTypeInfo>();
90          model = null;
91          isInitialized = false;
92      }
93  
94      //retrieve rules based on IDs stored in this program
95      public void retrieveProgramRequirements(Controller parentController, String modelId, final Callback<Boolean> onReadyCallback) {
96          
97          setupRules(parentController, modelId, new Callback<Boolean>() {
98              @Override
99              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 }