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