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  
16  package org.kuali.student.lum.lu.ui.course.client.requirements;
17  
18  import java.util.HashMap;
19  import java.util.List;
20  import java.util.Map;
21  
22  import org.kuali.student.common.assembly.data.Metadata;
23  import org.kuali.student.common.ui.client.application.KSAsyncCallback;
24  import org.kuali.student.common.ui.client.configurable.mvc.SectionTitle;
25  import org.kuali.student.common.ui.client.configurable.mvc.views.VerticalSectionView;
26  import org.kuali.student.common.ui.client.mvc.Callback;
27  import org.kuali.student.common.ui.client.service.MetadataRpcService;
28  import org.kuali.student.common.ui.client.service.MetadataRpcServiceAsync;
29  import org.kuali.student.common.ui.client.widgets.KSProgressIndicator;
30  import org.kuali.student.common.ui.client.widgets.buttongroups.ButtonEnumerations;
31  import org.kuali.student.common.ui.client.widgets.field.layout.button.ActionCancelGroup;
32  import org.kuali.student.common.ui.client.widgets.progress.BlockingTask;
33  import org.kuali.student.common.ui.client.widgets.progress.KSBlockingProgressIndicator;
34  import org.kuali.student.common.versionmanagement.dto.VersionDisplayInfo;
35  import org.kuali.student.core.statement.dto.ReqCompFieldInfo;
36  import org.kuali.student.core.statement.dto.ReqCompFieldTypeInfo;
37  import org.kuali.student.core.statement.dto.ReqComponentInfo;
38  import org.kuali.student.core.statement.dto.ReqComponentTypeInfo;
39  import org.kuali.student.core.statement.dto.StatementOperatorTypeKey;
40  import org.kuali.student.core.statement.dto.StatementTreeViewInfo;
41  import org.kuali.student.core.statement.ui.client.widgets.rules.ReqCompEditWidget;
42  import org.kuali.student.core.statement.ui.client.widgets.rules.ReqComponentInfoUi;
43  import org.kuali.student.core.statement.ui.client.widgets.rules.RuleManageWidget;
44  import org.kuali.student.core.statement.ui.client.widgets.rules.RulesUtil;
45  import org.kuali.student.lum.common.client.widgets.BuildCluSetWidget;
46  import org.kuali.student.lum.common.client.widgets.CluSetRetrieverImpl;
47  import org.kuali.student.lum.common.client.widgets.CourseWidget;
48  import org.kuali.student.lum.common.client.widgets.GradeWidget;
49  import org.kuali.student.lum.common.client.widgets.ProgramWidget;
50  import org.kuali.student.lum.lu.dto.CluInfo;
51  import org.kuali.student.lum.lu.ui.course.client.service.LuRpcService;
52  import org.kuali.student.lum.lu.ui.course.client.service.LuRpcServiceAsync;
53  import org.kuali.student.lum.program.client.rpc.StatementRpcService;
54  import org.kuali.student.lum.program.client.rpc.StatementRpcServiceAsync;
55  
56  import com.google.gwt.core.client.GWT;
57  import com.google.gwt.user.client.Window;
58  import com.google.gwt.user.client.rpc.AsyncCallback;
59  import com.google.gwt.user.client.ui.SimplePanel;
60  import com.google.gwt.user.client.ui.VerticalPanel;
61  import com.google.gwt.user.client.ui.Widget;
62  
63  public class CourseRequirementsManageView extends VerticalSectionView {
64  
65      private StatementRpcServiceAsync statementRpcServiceAsync = GWT.create(StatementRpcService.class);
66      private MetadataRpcServiceAsync metadataServiceAsync = GWT.create(MetadataRpcService.class);
67      private LuRpcServiceAsync luRpcServiceAsync = GWT.create(LuRpcService.class);    
68  
69      protected static final String TEMLATE_LANGUAGE = "en";
70      protected static final String RULEEDIT_TEMLATE = "KUALI.RULE";
71      protected static final String RULEPREVIEW_TEMLATE = "KUALI.RULE.PREVIEW";
72      protected static final String COMPOSITION_TEMLATE = "KUALI.RULE.COMPOSITION";
73      private static final String LU_NAMESPACE = "http://student.kuali.org/wsdl/lu";
74      private static final String CLU_NAMESPACE_URI = "{" + LU_NAMESPACE + "}cluInfo";
75  
76      private CourseRequirementsViewController parentController;
77  
78      //view's widgets
79      private VerticalPanel layout = new VerticalPanel();
80      private ReqCompEditWidget editReqCompWidget;
81      private RuleManageWidget ruleManageWidget;
82      private SimplePanel twiddlerPanel = new SimplePanel();
83      private ActionCancelGroup actionCancelButtons = new ActionCancelGroup(ButtonEnumerations.SaveCancelEnum.SAVE, ButtonEnumerations.SaveCancelEnum.CANCEL);
84  
85      //view's data
86      private StatementTreeViewInfo rule = null;
87      private boolean isInitialized = false;
88      private boolean isNewRule = false;
89      private ReqComponentInfo editedReqCompInfo = null;
90      private static int tempStmtTreeViewInfoID = 9999;
91      private Integer internalCourseReqID = null;
92      private String originalReqCompNL;
93      private String originalLogicExpression;
94  
95      //   private boolean isLocalDirty = false;
96      private boolean userClickedSaveButton = false;
97  	private BlockingTask creatingRuleTask = new BlockingTask("Creating Rule");
98  
99      public CourseRequirementsManageView(CourseRequirementsViewController parentController, Enum<?> viewEnum, String name, String modelId) {
100         super(viewEnum, name, modelId);
101         this.parentController = parentController;
102     }
103 
104     @Override
105     public void beforeShow(final Callback<Boolean> onReadyCallback) {
106 
107         retrieveAndSetupReqCompTypes(); //TODO cache it for each statement type?
108         if (!isInitialized) {
109             setupHandlers();
110             draw();
111             isInitialized = true;
112         }
113 
114         onReadyCallback.exec(true);
115     }
116 
117     private void setupHandlers() {
118         editReqCompWidget.setReqCompConfirmButtonClickCallback(actionButtonClickedReqCompCallback);
119         editReqCompWidget.setNewReqCompSelectedCallbackCallback(newReqCompSelectedCallbackCallback);
120         editReqCompWidget.setRetrieveCompositionTemplateCallback(retrieveCompositionTemplateCallback);
121         editReqCompWidget.setRetrieveFieldsMetadataCallback(retrieveFieldsMetadataCallback);
122         editReqCompWidget.setRetrieveCustomWidgetCallback(retrieveCustomWidgetCallback);
123     }
124 
125     private void draw() {
126 
127         remove(layout);
128         layout.clear();
129 
130         //STEP 1
131         SectionTitle title = SectionTitle.generateH3Title("Step 1: Build and Add Rules");
132         title.setStyleName("KS-Course-Requisites-Manage-Step-header1");
133         layout.add(title);
134 
135         layout.add(editReqCompWidget);
136 
137         //STEP 2
138         title = SectionTitle.generateH3Title("Step 2: Combine Rules with Logic");
139         title.setStyleName("KS-Course-Requisites-Manage-Step-header2");
140         layout.add(title);
141 
142         layout.add(ruleManageWidget);
143 
144         //add progressive indicator when rules are being simplified
145         KSProgressIndicator twiddler = new KSProgressIndicator();
146         twiddler.setVisible(false);
147         twiddlerPanel.setWidget(twiddler);
148         layout.add(twiddlerPanel);
149 
150         addWidget(layout);
151 
152         displaySaveButton();
153     }
154 
155     private void displaySaveButton() {
156         actionCancelButtons.addStyleName("KS-Course-Requisites-Save-Button");
157         actionCancelButtons.addCallback(new Callback<ButtonEnumerations.ButtonEnum>(){
158              @Override
159             public void exec(ButtonEnumerations.ButtonEnum result) {
160                 userClickedSaveButton = (result == ButtonEnumerations.SaveCancelEnum.SAVE);
161                 parentController.showView(CourseRequirementsViewController.CourseRequirementsViews.PREVIEW);
162             }
163         });
164         addWidget(actionCancelButtons);
165     }
166 
167     // called by requirement display widget when user wants to edit or add a sub-rule
168     public void setRuleTree(StatementTreeViewInfo stmtTreeInfo, boolean newRuleFlag, Integer internalCourseReqID) {
169 
170         if (!isInitialized) {
171             editReqCompWidget = new ReqCompEditWidget(CourseRequirementsSummaryView.NEW_REQ_COMP_ID);
172             ruleManageWidget = new RuleManageWidget();
173             ruleManageWidget.setReqCompEditButtonClickCallback(editReqCompCallback);
174             ruleManageWidget.setRuleChangedButtonClickCallback(ruleChangedCallback);
175         }
176 
177         this.internalCourseReqID = internalCourseReqID;
178         editedReqCompInfo = null;
179         userClickedSaveButton = false;
180         rule = RulesUtil.clone(stmtTreeInfo);
181         isNewRule = newRuleFlag;
182         originalReqCompNL = getAllReqCompNLs();
183 
184         //update screen elements
185         editReqCompWidget.setupNewReqComp();
186         ruleManageWidget.redraw(rule, false);
187        // originalLogicExpression = ruleManageWidget.getLogicExpression();
188     }
189 
190     //retrieve the latest version from rule table widget and update the local copy
191     public StatementTreeViewInfo getRuleTree() {
192         rule = ruleManageWidget.getStatementTreeViewInfo();
193         return rule;
194     }
195 
196     public boolean isNewRule() {
197         return isNewRule;
198     }
199 
200     //called when user clicked on rule 'edit' link
201     protected Callback<ReqComponentInfo> editReqCompCallback = new Callback<ReqComponentInfo>(){
202         public void exec(ReqComponentInfo reqComp) {
203             setEnabled(false);
204             editReqCompWidget.setupExistingReqComp(reqComp);
205             editedReqCompInfo = reqComp;
206         }
207     };
208 
209     protected Callback<Boolean> ruleChangedCallback = new Callback<Boolean>(){
210         public void exec(Boolean ruleChanged) {
211             actionCancelButtons.getButton(ButtonEnumerations.SaveCancelEnum.SAVE).setEnabled(ruleChanged);
212         }
213     };
214 
215     protected void setEnabled(boolean enabled) {
216         ruleManageWidget.setEnabled(enabled);
217         actionCancelButtons.getButton(ButtonEnumerations.SaveCancelEnum.SAVE).setEnabled(enabled);
218     }
219 
220     @Override
221     public boolean isDirty() {
222         if (!isInitialized) {
223             return false;
224         }
225 
226         //TODO until we figure out how to detect changes, always return true
227         return true;
228 
229         //first check logic expression
230 //        if (!ruleManageWidget.getLogicExpression().equals(originalLogicExpression)) {
231 //            return true;
232 //        }
233 
234         //next check NL for req. components
235       //  if ((originalNL == null) && (rule.getNaturalLanguageTranslation() == null)) {
236       //      return !ruleManageWidget.getLogicExpression().equals(originalLogicExpression);
237       //  }
238         //TODO how to check whether rule changed or not?
239        // !(ruleManageWidget.getLogicExpression().equals(originalLogicExpression) && getAllReqCompNLs().equals(originalReqCompNL));
240     }
241 
242     private String getAllReqCompNLs() {
243         StringBuilder NL = new StringBuilder();
244         for (StatementTreeViewInfo tree : rule.getStatements()) {
245             for (ReqComponentInfo reqComp : tree.getReqComponents()) {
246                 NL.append(reqComp.getNaturalLanguageTranslation());
247             }
248         }
249         return NL.toString();
250     }
251 
252     //called when user clicks 'Add Rule' or 'Update Rule' when editing a req. component
253     protected Callback<ReqComponentInfoUi> actionButtonClickedReqCompCallback = new Callback<ReqComponentInfoUi>(){
254         public void exec(final ReqComponentInfoUi reqComp) {
255 
256             editReqCompWidget.setupNewReqComp();
257             setEnabled(true);
258 
259             //true if user cancel adding/editing req. component
260             if (reqComp == null) {
261                 return;
262             }
263 
264             KSBlockingProgressIndicator.addTask(creatingRuleTask);
265 
266             //1. update NL for the req. component
267             statementRpcServiceAsync.translateReqComponentToNLs(reqComp, new String[]{RULEEDIT_TEMLATE, RULEPREVIEW_TEMLATE}, TEMLATE_LANGUAGE, new KSAsyncCallback<List<String>>() {
268                 public void handleFailure(Throwable caught) {
269                     KSBlockingProgressIndicator.removeTask(creatingRuleTask);
270                     Window.alert(caught.getMessage());
271                     GWT.log("translateReqComponentToNL failed", caught);
272                }
273 
274                 public void onSuccess(final List<String> reqCompNL) {
275 
276                     reqComp.setNaturalLanguageTranslation(reqCompNL.get(0));
277                     reqComp.setPreviewNaturalLanguageTranslation(reqCompNL.get(1));
278 
279                     //2. add / update req. component
280                     rule = ruleManageWidget.getStatementTreeViewInfo();  //TODO ?
281 
282                     if (editedReqCompInfo == null) {  //add req. component
283                         if (rule.getStatements() != null && !rule.getStatements().isEmpty()) {
284                             StatementTreeViewInfo newStatementTreeViewInfo = new StatementTreeViewInfo();
285                             newStatementTreeViewInfo.setId(CourseRequirementsSummaryView.NEW_STMT_TREE_ID + Integer.toString(tempStmtTreeViewInfoID++));
286                             newStatementTreeViewInfo.setOperator(rule.getStatements().get(0).getOperator());
287                             newStatementTreeViewInfo.getReqComponents().add(reqComp);
288                             rule.getStatements().add(newStatementTreeViewInfo);
289                         } else {
290                             rule.getReqComponents().add(reqComp);
291                             //set default operator between req. components of the rule
292                             if (rule.getOperator() == null) {
293                                 rule.setOperator(StatementOperatorTypeKey.AND);
294                             }
295                         }
296                     } else {    //update req. component
297                         editedReqCompInfo.setNaturalLanguageTranslation(reqComp.getNaturalLanguageTranslation());
298                         editedReqCompInfo.setReqCompFields(reqComp.getReqCompFields());
299                         editedReqCompInfo.setType(reqComp.getType());
300                         editedReqCompInfo = null;  //de-reference from existing req. component
301                     }
302 
303                     ruleManageWidget.redraw(rule, true);
304                     KSBlockingProgressIndicator.removeTask(creatingRuleTask);
305                 }
306             });
307         }
308     };
309 
310     //called when user selects a rule type in the editor
311     protected Callback<ReqComponentInfo> newReqCompSelectedCallbackCallback = new Callback<ReqComponentInfo>(){
312         public void exec(final ReqComponentInfo reqComp) {
313             setEnabled(false);
314         }
315     };
316 
317     private void retrieveAndSetupReqCompTypes() {
318 
319         statementRpcServiceAsync.getReqComponentTypesForStatementType(rule.getType(), new KSAsyncCallback<List<ReqComponentTypeInfo>>() {
320             public void handleFailure(Throwable cause) {
321             	GWT.log("Failed to get req. component types for statement of type:" + rule.getType(), cause);
322             	Window.alert("Failed to get req. component types for statement of type:" + rule.getType());
323             }
324 
325             public void onSuccess(final List<ReqComponentTypeInfo> reqComponentTypeInfoList) {
326                 if (reqComponentTypeInfoList == null || reqComponentTypeInfoList.size() == 0) {
327                     GWT.log("Missing Requirement Component Types", null);
328                     Window.alert("Missing Requirement Component Types");
329                     return;
330                 }
331                 editReqCompWidget.setReqCompList(reqComponentTypeInfoList);
332                 editReqCompWidget.setCustomWidgets(getCustomWidgets(reqComponentTypeInfoList));                
333             }
334         });
335     }
336 
337     private Map<String, Widget> getCustomWidgets(List<ReqComponentTypeInfo> reqComponentTypeInfoList) {
338         Map<String, Widget> customWidgets = new HashMap<String, Widget>();
339 
340         for (ReqComponentTypeInfo reqCompTypeInfo : reqComponentTypeInfoList) {
341             for (ReqCompFieldTypeInfo fieldTypeInfo : reqCompTypeInfo.getReqCompFieldTypeInfos()) {
342                 if (RulesUtil.isGradeWidget(fieldTypeInfo.getId())) {
343                     customWidgets.put("kuali.reqComponent.field.type.grade.id", new GradeWidget());
344                 } else if (RulesUtil.isCourseWidget(fieldTypeInfo.getId())) {
345 
346                     final CourseWidget courseWidget = new CourseWidget();
347                     
348                     courseWidget.addGetCluNameCallback(new Callback() {
349 
350                         @Override
351                         public void exec(Object id) {
352 
353                             luRpcServiceAsync.getCurrentVersion(CLU_NAMESPACE_URI, (String)id, new AsyncCallback<VersionDisplayInfo>() {
354                                 @Override
355                                 public void onFailure(Throwable throwable) {
356                                     Window.alert(throwable.getMessage());
357                                     GWT.log("Failed to retrieve clu for id: '" +  "'", throwable);
358                                 }
359 
360                                 @Override
361                                 public void onSuccess(final VersionDisplayInfo versionInfo) {
362                                     luRpcServiceAsync.getClu(versionInfo.getId(), new AsyncCallback<CluInfo>() {
363                                         @Override
364                                         public void onFailure(Throwable throwable) {
365                                             Window.alert(throwable.getMessage());
366                                             GWT.log("Failed to retrieve clu", throwable);
367                                         }
368 
369                                         @Override
370                                         public void onSuccess(CluInfo cluInfo) {
371                                             courseWidget.setLabelContent(cluInfo.getVersionInfo().getVersionIndId(), cluInfo.getOfficialIdentifier().getCode());
372                                         }
373                                     });
374                                 }
375                             });
376 
377 
378                         }
379                     });
380 
381                     customWidgets.put("kuali.reqComponent.field.type.course.clu.id", courseWidget);
382                 } else if (RulesUtil.isProgramWidget(fieldTypeInfo.getId())) {
383                     final ProgramWidget programWidget = new ProgramWidget();
384 
385                     programWidget.addGetCluNameCallback(new Callback() {
386 
387                         @Override
388                         public void exec(Object id) {
389 
390                             statementRpcServiceAsync.getCurrentVersion(CLU_NAMESPACE_URI, (String)id, new AsyncCallback<VersionDisplayInfo>() {
391                                 @Override
392                                 public void onFailure(Throwable throwable) {
393                                     Window.alert(throwable.getMessage());
394                                     GWT.log("Failed to retrieve clu for id: '" +  "'", throwable);
395                                 }
396 
397                                 @Override
398                                 public void onSuccess(final VersionDisplayInfo versionInfo) {
399                                     statementRpcServiceAsync.getClu(versionInfo.getId(), new AsyncCallback<CluInfo>() {
400                                         @Override
401                                         public void onFailure(Throwable throwable) {
402                                             Window.alert(throwable.getMessage());
403                                             GWT.log("Failed to retrieve clu", throwable);
404                                         }
405 
406                                         @Override
407                                         public void onSuccess(CluInfo cluInfo) {
408                                             programWidget.setLabelContent(cluInfo.getVersionInfo().getVersionIndId(), cluInfo.getOfficialIdentifier().getCode());
409                                         }
410                                     });
411                                 }
412                             });
413                         }
414                     });
415 
416                     customWidgets.put("kuali.reqComponent.field.type.program.clu.id", programWidget);
417                 }
418             }
419         }
420         return customWidgets;
421     }
422 
423     //called when user selects a rule type in the rule editor
424     protected Callback<ReqComponentInfo> retrieveCompositionTemplateCallback = new Callback<ReqComponentInfo>(){
425         public void exec(final ReqComponentInfo reqComp) {
426             statementRpcServiceAsync.translateReqComponentToNL(reqComp, COMPOSITION_TEMLATE, TEMLATE_LANGUAGE, new KSAsyncCallback<String>() {
427                 public void handleFailure(Throwable caught) {
428                     Window.alert(caught.getMessage());
429                     GWT.log("translateReqComponentToNL failed for req. comp. type: '" + reqComp.getType() + "'",caught);
430                 }
431 
432                 public void onSuccess(final String compositionTemplate) {
433                     editReqCompWidget.displayFieldsStart(compositionTemplate);
434                 }
435             });
436         }
437     };
438 
439     protected Callback<List<String>> retrieveFieldsMetadataCallback = new Callback<List<String>>(){
440         public void exec(final List<String> fieldTypes) {
441 
442             if (fieldTypes.contains("kuali.reqComponent.field.type.grade.id")) {
443                 fieldTypes.add("kuali.reqComponent.field.type.gradeType.id");
444             }
445 
446             metadataServiceAsync.getMetadataList(ReqCompFieldInfo.class.getName(), fieldTypes, null, new KSAsyncCallback<List<Metadata>>() {
447                 public void handleFailure(Throwable caught) {
448                     Window.alert(caught.getMessage());
449                     GWT.log("getMetadataList failed for req. comp. types: '" + fieldTypes.toString() + "'",caught);
450                 }
451 
452                 public void onSuccess(final List<Metadata> metadataList) {
453                     editReqCompWidget.displayFieldsEnd(metadataList);
454                 }
455             });
456         }
457     };
458 
459     protected Callback<String> retrieveCustomWidgetCallback = new Callback<String>(){
460         public void exec(final String fieldType) {
461             if (RulesUtil.isCluSetWidget(fieldType)) {
462                 String clusetType = "kuali.cluSet.type.Course";
463                 if (fieldType.toLowerCase().indexOf("program") > 0) {
464                     clusetType = "kuali.cluSet.type.Program";
465                 }
466                 editReqCompWidget.displayCustomWidget(fieldType, new BuildCluSetWidget(new CluSetRetrieverImpl(), clusetType, false));
467             }
468         }
469     };
470 
471     public boolean isUserClickedSaveButton() {
472         return userClickedSaveButton;
473     }
474 
475     public void setUserClickedSaveButton(boolean userClickedSaveButton) {
476         this.userClickedSaveButton = userClickedSaveButton;
477     }
478 
479     public Integer getInternalCourseReqID() {
480         return internalCourseReqID;
481     }
482 }