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    }