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.common.assembly.data.Data;
20  import org.kuali.student.common.ui.client.application.KSAsyncCallback;
21  import org.kuali.student.common.ui.client.mvc.*;
22  import org.kuali.student.core.statement.dto.StatementTreeViewInfo;
23  import org.kuali.student.core.statement.dto.StatementTypeInfo;
24  import org.kuali.student.core.statement.ui.client.widgets.rules.RulesUtil;
25  import org.kuali.student.lum.program.client.ProgramConstants;
26  import org.kuali.student.lum.program.client.events.StoreRequirementIDsEvent;
27  import org.kuali.student.lum.program.client.events.StoreSpecRequirementIDsEvent;
28  import org.kuali.student.lum.program.client.rpc.MajorDisciplineRpcService;
29  import org.kuali.student.lum.program.client.rpc.MajorDisciplineRpcServiceAsync;
30  import org.kuali.student.lum.program.client.rpc.StatementRpcService;
31  import org.kuali.student.lum.program.client.rpc.StatementRpcServiceAsync;
32  import org.kuali.student.lum.program.dto.ProgramRequirementInfo;
33  
34  import com.google.gwt.core.client.GWT;
35  import com.google.gwt.event.shared.HandlerManager;
36  import com.google.gwt.user.client.Window;
37  
38  public class ProgramRequirementsDataModel {
39  
40      private final MajorDisciplineRpcServiceAsync programRemoteService = GWT.create(MajorDisciplineRpcService.class);
41      private StatementRpcServiceAsync statementRpcServiceAsync = GWT.create(StatementRpcService.class);
42      private Model model = null;
43      private HandlerManager eventBus;
44  
45      //keeping track of rules and rule state
46      public enum requirementState {STORED, ADDED, EDITED, DELETED;}
47      private Map<Integer, ProgramRequirementInfo> progReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>();
48      private Map<Integer, ProgramRequirementInfo> origProgReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>();
49      private Map<Integer, requirementState> progReqState = new HashMap<Integer, requirementState>();
50      private Map<Integer, requirementState> origProgReqState = new HashMap<Integer, requirementState>();
51      private List<StatementTypeInfo> stmtTypes = new ArrayList<StatementTypeInfo>();
52      private boolean isInitialized = false;
53      private static Integer progReqIDs = 111111;
54  
55      public ProgramRequirementsDataModel(HandlerManager eventBus) {
56          this.eventBus = eventBus;
57      }
58  
59      //find out whether we need to reset rules based on whether we have a new program ID or not
60      public void setupRules(Controller parentController, String modelId, final Callback<Boolean> onReadyCallback) {
61          parentController.requestModel(modelId, new ModelRequestCallback() {
62  
63              @Override
64              public void onRequestFail(Throwable cause) {
65                  GWT.log("Unable to retrieve program model for program summary view", cause);
66                  onReadyCallback.exec(false);
67              }
68  
69              @Override
70              public void onModelReady(Model modelIn) {
71                  //TODO how can we reliably know that we need to reload rules (or not)
72                  //String programId = (model == null ? null : (String)((DataModel)model).getRoot().get("id"));
73                  //String modelProgramId = ((DataModel)modelIn).getRoot().get(ProgramConstants.ID);
74                  //if ((modelProgramId == null) || (!modelProgramId.equals(programId))) {
75                      resetRules();
76                  //}
77                  model = modelIn;
78                  onReadyCallback.exec(true);
79              }
80          });
81      }
82  
83      private void resetRules() {
84          progReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>();
85          origProgReqInfos = new LinkedHashMap<Integer, ProgramRequirementInfo>();
86          progReqState = new HashMap<Integer, requirementState>();
87          origProgReqState = new HashMap<Integer, requirementState>();
88          stmtTypes = new ArrayList<StatementTypeInfo>();
89          model = null;
90          isInitialized = false;
91      }
92  
93      //retrieve rules based on IDs stored in this program
94      public void retrieveProgramRequirements(Controller parentController, String modelId, final Callback<Boolean> onReadyCallback) {
95          
96          setupRules(parentController, modelId, new Callback<Boolean>() {
97              @Override
98              public void exec(Boolean result) {
99                  Data program = ((DataModel)model).getRoot().get(ProgramConstants.PROGRAM_REQUIREMENTS);
100 
101                 Iterator<Data.Property> realPropertyIterator = program.realPropertyIterator();
102                 ArrayList<String> programRequirementIds = new ArrayList<String>();
103                 while(realPropertyIterator.hasNext()) {
104                     programRequirementIds.add((String)realPropertyIterator.next().getValue());
105                 }
106                 retrieveStatementTypes(programRequirementIds, onReadyCallback);
107             }
108         });               
109     }
110 
111     private void retrieveStatementTypes(final List<String> programRequirementIds, final Callback<Boolean> onReadyCallback) {
112 
113         //retrieve available program requirement types
114         statementRpcServiceAsync.getStatementTypesForStatementType("kuali.statement.type.program", new KSAsyncCallback<List<StatementTypeInfo>>() {
115             @Override
116             public void handleFailure(Throwable caught) {
117 	            Window.alert(caught.getMessage());
118 	            GWT.log("getStatementTypes failed", caught);
119                 onReadyCallback.exec(false);
120             }
121 
122             @Override
123             public void onSuccess(List<StatementTypeInfo> stmtInfoTypes) {
124                 //store the statement types
125                 for (StatementTypeInfo stmtInfoType : stmtInfoTypes) {
126                     stmtTypes.add(stmtInfoType);
127                 }
128 
129                 //now retrieve the actual rules
130                 retrieveRules(programRequirementIds, onReadyCallback);
131             }
132         });
133     }
134 
135     private void retrieveRules(List<String> programRequirementIds, final Callback<Boolean> onReadyCallback) {
136 
137         //true if no program requirements exist yet
138         if ((programRequirementIds == null) || programRequirementIds.isEmpty()) {
139             isInitialized = true;
140             onReadyCallback.exec(true);
141             return;
142         }
143 
144         programRemoteService.getProgramRequirements(programRequirementIds, new KSAsyncCallback<List<ProgramRequirementInfo>>() {
145             @Override
146             public void handleFailure(Throwable caught) {
147                 Window.alert(caught.getMessage());
148                 GWT.log("getProgramRequirement failed", caught);
149                 onReadyCallback.exec(false);
150             }
151 
152             @Override
153             public void onSuccess(List<ProgramRequirementInfo> programReqInfos) {
154                 //update rules list with new program requirements
155                 origProgReqInfos.clear();
156                 origProgReqState.clear();
157                 progReqInfos.clear();
158                 progReqState.clear();
159                 for (ProgramRequirementInfo programReqInfo : programReqInfos) {
160 
161                     if (getStmtTypeInfo(programReqInfo.getStatement().getType()) == null) {
162                         Window.alert("Did not find corresponding statement type for program requirement of type: " + programReqInfo.getStatement().getType());
163                         GWT.log("Did not find corresponding statement type for program requirement of type: " + programReqInfo.getStatement().getType(), null);
164                     }
165 
166                     setRuleState(programReqInfo);
167                     origProgReqInfos.put(progReqIDs, cloneProgReq(programReqInfo));
168                     origProgReqState.put(progReqIDs, requirementState.STORED);
169                     progReqInfos.put(progReqIDs, programReqInfo);
170                     progReqState.put(progReqIDs++, requirementState.STORED);
171                 }
172 
173                 isInitialized = true;
174                 onReadyCallback.exec(true);
175             }
176         });     
177     }
178 
179     public ProgramRequirementInfo updateRules(StatementTreeViewInfo newSubRule, Integer internalProgReqID, boolean isNewRule) {
180 
181         ProgramRequirementInfo affectedRule = progReqInfos.get(internalProgReqID);
182 
183         if (affectedRule == null) {
184             Window.alert("Cannot find program requirement with a statement that has id: '" + newSubRule.getId() + "'");
185             GWT.log("Cannot find program requirement with a statement that has id: '" + newSubRule.getId() + "'", null);
186             return null;
187         }
188 
189         if (progReqState.get(internalProgReqID) == ProgramRequirementsDataModel.requirementState.STORED) {
190             progReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.EDITED);
191         }
192 
193         //if we don't have top level req. components wrapped in statement, do so before we add another statement
194         StatementTreeViewInfo affectedTopTree = affectedRule.getStatement();
195         if ((affectedTopTree.getReqComponents() != null) && !affectedTopTree.getReqComponents().isEmpty()) {
196             StatementTreeViewInfo stmtTree = new StatementTreeViewInfo();
197             stmtTree.setId(ProgramRequirementsSummaryView.generateStatementTreeId());
198             stmtTree.setType( affectedRule.getStatement().getType());
199             stmtTree.setReqComponents(affectedTopTree.getReqComponents());
200             List<StatementTreeViewInfo> stmtList = new ArrayList<StatementTreeViewInfo>();
201             stmtList.add(stmtTree);
202             affectedTopTree.setStatements(stmtList);
203         }
204 
205         //now update the actual rule
206         List<StatementTreeViewInfo> affectedSubRules = affectedTopTree.getStatements();
207         if (isNewRule) {
208             affectedSubRules.add(newSubRule);
209         } else {
210             //update rule
211             if (affectedSubRules == null || affectedSubRules.isEmpty()) {
212                 affectedRule.setStatement(newSubRule);
213             } else { //replace the related stored subrule with a new version
214                 for (StatementTreeViewInfo subRule : affectedSubRules) {
215                     if (subRule.getId().equals(newSubRule.getId())) {
216                         int treeIx = affectedSubRules.indexOf(subRule);
217                         //only update if the rule is not empty
218                         if (!isEmptyRule(newSubRule)) {
219                             affectedSubRules.add(treeIx, newSubRule);
220                         }
221                         affectedSubRules.remove(subRule);
222                         break;
223                     }
224                 }
225             }
226         }
227 
228         return affectedRule;
229     }
230 
231     public void updateProgramEntities(final Callback<List<ProgramRequirementInfo>> callback) {
232 
233         final List<String> referencedProgReqIds = new ArrayList<String>();
234 
235         programRemoteService.storeProgramRequirements(progReqState, progReqInfos, new KSAsyncCallback<Map<Integer, ProgramRequirementInfo>>() {
236             @Override
237             public void handleFailure(Throwable caught) {
238                 Window.alert(caught.getMessage());
239                 GWT.log("storeProgramRequirements failed", caught);
240             }
241             @Override
242             public void onSuccess(Map<Integer, ProgramRequirementInfo> storedRules) {
243 
244                 for (Integer internalProgReqID : storedRules.keySet()) {
245                     ProgramRequirementInfo storedRule = storedRules.get(internalProgReqID);
246                     switch (progReqState.get(internalProgReqID)) {
247                         case STORED:
248                             //rule was not changed so continue
249                             referencedProgReqIds.add(progReqInfos.get(internalProgReqID).getId());
250                             break;
251                         case ADDED:
252                             referencedProgReqIds.add(storedRule.getId());
253                             progReqInfos.put(internalProgReqID, storedRule);
254                             origProgReqInfos.put(internalProgReqID, cloneProgReq(storedRule));
255                             origProgReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.STORED);
256                             progReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.STORED);
257                             break;
258                         case EDITED:
259                             referencedProgReqIds.add(storedRule.getId());
260                             progReqInfos.put(internalProgReqID, storedRule);
261                             origProgReqInfos.put(internalProgReqID, cloneProgReq(storedRule));
262                             origProgReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.STORED);
263                             progReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.STORED);
264                             break;
265                         case DELETED:
266                             progReqInfos.remove(internalProgReqID);
267                             origProgReqInfos.remove(internalProgReqID);
268                             origProgReqState.remove(internalProgReqID);                            
269                             progReqState.remove(internalProgReqID);
270                             break;
271                         default:
272                             break;
273                     }
274                 }
275 
276                 saveRequirementIds(referencedProgReqIds, storedRules, callback);
277             }
278         });        
279     }
280 
281     private void saveRequirementIds(final List<String> referencedProgReqIds, final Map<Integer, ProgramRequirementInfo> storedRules, final Callback<List<ProgramRequirementInfo>> callback) {
282         String programId = ((DataModel)model).getRoot().get("id");
283         String programType = ((DataModel)model).getRoot().get("type");
284 
285         //for some reason, credential program has type stored in 'credentialProgramType'
286         if (programType == null) {
287             programType = ((DataModel)model).getRoot().get("credentialProgramType");    
288         }
289 
290         //specializations will be handled differently from Major
291         if (programType.equals(ProgramConstants.VARIATION_TYPE_KEY)) {
292             eventBus.fireEvent(new StoreSpecRequirementIDsEvent(programId, programType, referencedProgReqIds));
293         } else {
294             eventBus.fireEvent(new StoreRequirementIDsEvent(programId, programType, referencedProgReqIds));
295         }
296 
297         callback.exec(new ArrayList(storedRules.values()));  //update display widgets
298     }
299 
300 
301     public List<ProgramRequirementInfo> getProgReqInfo(String stmtTypeId) {
302         List<ProgramRequirementInfo> rules = new ArrayList<ProgramRequirementInfo>();
303         for(ProgramRequirementInfo progReqInfo : progReqInfos.values()) {
304             if (progReqInfo.getStatement().getType().equals(stmtTypeId)) {
305                 rules.add(progReqInfo);
306             }
307         }
308         return rules;
309     }
310 
311     public Integer getInternalProgReqID(ProgramRequirementInfo progReqInfo) {
312         for(Integer key : progReqInfos.keySet()) {
313             if (progReqInfos.get(key) == progReqInfo) {
314                 return key;
315             }
316         }
317 
318         Window.alert("Problem retrieving key for program requirement: " + progReqInfo.getId());
319         GWT.log("Problem retrieving key for program requirement: " + progReqInfo.getId(), null);        
320 
321         return null;
322     }
323 
324     public StatementTypeInfo getStmtTypeInfo(String stmtTypeId) {
325         for (StatementTypeInfo stmtInfo : stmtTypes) {
326             if (stmtInfo.getId().equals(stmtTypeId)) {
327                 return stmtInfo;
328             }
329         }
330 
331         Window.alert("Did not find StatementTypeInfo based on type: " + stmtTypeId);
332         GWT.log("Did not find StatementTypeInfo based on type: " + stmtTypeId);
333 
334         return null;
335     }
336 
337     public void deleteRule(Integer internalProgReqID) {
338         if (progReqState.get(internalProgReqID) == ProgramRequirementsDataModel.requirementState.ADDED) {
339             //user added a rule, didn't save it and now wants to delete it
340             progReqState.remove(internalProgReqID);
341             progReqInfos.remove(internalProgReqID);
342         } else {
343             markRuleAsDeleted(internalProgReqID);
344         }
345     }
346 
347     public void addRule(ProgramRequirementInfo programReqInfo) { 
348     	setRuleState(programReqInfo);
349         progReqInfos.put(progReqIDs, programReqInfo);
350         progReqState.put(progReqIDs++, requirementState.ADDED);
351     }
352 
353     public void updateRule(Integer internalProgReqID, ProgramRequirementInfo programReqInfo) {
354     	setRuleState(programReqInfo);    	
355         progReqInfos.put(internalProgReqID, programReqInfo);
356         markRuleAsEdited(internalProgReqID);
357     }
358     
359     /**
360      * Set the state of the program requirement to state of the program.
361      * 
362      * @param programReqInfo
363      */
364     protected void setRuleState(ProgramRequirementInfo programReqInfo) {
365         if (model != null) {
366             String programState = ((DataModel) model).get(ProgramConstants.STATE);
367             programReqInfo.setState(programState);
368         }
369     }
370 
371     public void markRuleAsDeleted(Integer internalProgReqID) {
372         if ((progReqState.get(internalProgReqID) == ProgramRequirementsDataModel.requirementState.STORED) ||
373             (progReqState.get(internalProgReqID) == ProgramRequirementsDataModel.requirementState.EDITED)) {
374             progReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.DELETED);
375         }
376     }
377 
378     public void markRuleAsEdited(Integer internalProgReqID) {
379         if (progReqState.get(internalProgReqID) == ProgramRequirementsDataModel.requirementState.STORED) {
380             progReqState.put(internalProgReqID, ProgramRequirementsDataModel.requirementState.EDITED);
381         }
382     }
383 
384     public String getStmtTypeName(String stmtTypeId) {
385         String name = getStmtTypeInfo(stmtTypeId).getName();
386         return (name == null ? "" : name);
387     }
388     
389     public boolean isRuleExists(String stmtTypeId) {
390         boolean showNoRuleText = true;
391         for(ProgramRequirementInfo ruleInfo : progReqInfos.values()) {
392             if ((ruleInfo.getStatement().getType().equals(stmtTypeId)) && (progReqState.get(getInternalProgReqID(ruleInfo)) != ProgramRequirementsDataModel.requirementState.DELETED)) {
393                 showNoRuleText = false;
394             }
395         }
396         return showNoRuleText;
397     }
398 
399     public boolean isDirty() {
400 
401         if (progReqState.size() != origProgReqState.size()) {
402             return true;
403         }
404 
405         for(Integer key : progReqState.keySet()) {
406             if (!progReqState.get(key).equals(origProgReqState.get(key))) {
407                 return true;
408             }
409         }
410 
411         /*
412         for(Integer key : progReqInfos.keySet()) {
413             if (!progReqInfos.get(key).equals(origProgReqInfos.get(key))) {
414                 return true;
415             }
416         } */
417         return false;
418     }
419 
420     public void revertRuleChanges() {
421         progReqInfos = new HashMap<Integer, ProgramRequirementInfo>();
422         progReqState = new HashMap<Integer, requirementState>();
423         for(Integer key : origProgReqInfos.keySet()) {
424             progReqInfos.put(key, cloneProgReq(origProgReqInfos.get(key)));
425             progReqState.put(key, origProgReqState.get(key));
426         }
427     }
428 
429     public ProgramRequirementInfo getProgReqByInternalId(Integer internalProgReqID) {
430         return progReqInfos.get(internalProgReqID);
431     }
432 
433     public boolean isEmptyRule(StatementTreeViewInfo tree) {
434         return (tree.getStatements() == null || tree.getStatements().isEmpty() && (tree.getReqComponents() == null || tree.getReqComponents().isEmpty()));
435     }  
436 
437     public boolean isInitialized() {
438         return isInitialized;
439     }
440 
441     public void setInitialized(boolean initialized) {
442         isInitialized = initialized;
443     }
444 
445     public List<StatementTypeInfo> getStmtTypes() {
446         return stmtTypes;
447     }
448 
449     private ProgramRequirementInfo cloneProgReq(ProgramRequirementInfo inProgReqInfo) {
450         ProgramRequirementInfo clonedProgReqInfo = null;
451         if (inProgReqInfo != null) {
452             clonedProgReqInfo = new ProgramRequirementInfo();
453             clonedProgReqInfo.setId(inProgReqInfo.getId());
454             clonedProgReqInfo.setShortTitle(inProgReqInfo.getShortTitle());
455             clonedProgReqInfo.setLongTitle(inProgReqInfo.getLongTitle());
456             clonedProgReqInfo.setDescr(inProgReqInfo.getDescr());
457             clonedProgReqInfo.setMinCredits(inProgReqInfo.getMinCredits());
458             clonedProgReqInfo.setMaxCredits(inProgReqInfo.getMaxCredits());
459             clonedProgReqInfo.setState(inProgReqInfo.getState());
460             clonedProgReqInfo.setType(inProgReqInfo.getType());
461             clonedProgReqInfo.setStatement(RulesUtil.clone(inProgReqInfo.getStatement()));
462             //TODO clonedProgReqInfo.setAttributes();
463             //TODO clonedProgReqInfo.setLearningObjectives();
464             //TODO clonedProgReqInfo.setMetaInfo();
465         }
466         return clonedProgReqInfo;
467     }  
468 }