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