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.program.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.mvc.View;
28  import org.kuali.student.common.ui.client.service.MetadataRpcService;
29  import org.kuali.student.common.ui.client.service.MetadataRpcServiceAsync;
30  import org.kuali.student.common.ui.client.widgets.KSProgressIndicator;
31  import org.kuali.student.common.ui.client.widgets.buttongroups.ButtonEnumerations;
32  import org.kuali.student.common.ui.client.widgets.field.layout.button.ActionCancelGroup;
33  import org.kuali.student.common.ui.client.widgets.progress.BlockingTask;
34  import org.kuali.student.common.ui.client.widgets.progress.KSBlockingProgressIndicator;
35  import org.kuali.student.common.versionmanagement.dto.VersionDisplayInfo;
36  import org.kuali.student.core.statement.dto.*;
37  import org.kuali.student.core.statement.ui.client.widgets.rules.ReqCompEditWidget;
38  import org.kuali.student.core.statement.ui.client.widgets.rules.ReqComponentInfoUi;
39  import org.kuali.student.core.statement.ui.client.widgets.rules.RuleManageWidget;
40  import org.kuali.student.core.statement.ui.client.widgets.rules.RulesUtil;
41  import org.kuali.student.lum.common.client.widgets.*;
42  import org.kuali.student.lum.lu.dto.CluInfo;
43  import org.kuali.student.lum.program.client.properties.ProgramProperties;
44  import org.kuali.student.lum.program.client.rpc.StatementRpcService;
45  import org.kuali.student.lum.program.client.rpc.StatementRpcServiceAsync;
46  import org.kuali.student.lum.program.dto.ProgramRequirementInfo;
47  
48  
49  import com.google.gwt.core.client.GWT;
50  import com.google.gwt.user.client.Window;
51  import com.google.gwt.user.client.rpc.AsyncCallback;
52  import com.google.gwt.user.client.ui.SimplePanel;
53  import com.google.gwt.user.client.ui.VerticalPanel;
54  import com.google.gwt.user.client.ui.Widget;
55  
56  public class ProgramRequirementsManageView extends VerticalSectionView {
57  
58      private StatementRpcServiceAsync statementRpcServiceAsync = GWT.create(StatementRpcService.class);
59      private MetadataRpcServiceAsync metadataServiceAsync = GWT.create(MetadataRpcService.class);   
60  
61      protected static final String TEMLATE_LANGUAGE = "en";
62      protected static final String RULEEDIT_TEMLATE = "KUALI.RULE";
63      protected static final String RULEPREVIEW_TEMLATE = RULEEDIT_TEMLATE + ".PREVIEW";
64      protected static final String COMPOSITION_TEMLATE = "KUALI.RULE.COMPOSITION";
65      private static final String LU_NAMESPACE = "http://student.kuali.org/wsdl/lu";
66      private static final String CLU_NAMESPACE_URI = "{" + LU_NAMESPACE + "}cluInfo";
67  
68      private ProgramRequirementsViewController parentController;
69  
70      //view's widgets
71      private VerticalPanel layout = new VerticalPanel();
72      private ReqCompEditWidget editReqCompWidget;
73      private RuleManageWidget ruleManageWidget;
74      private SimplePanel twiddlerPanel = new SimplePanel();
75      private ActionCancelGroup actionCancelButtons = new ActionCancelGroup(ButtonEnumerations.SaveCancelEnum.SAVE, ButtonEnumerations.SaveCancelEnum.CANCEL);
76  
77      //view's data
78      private StatementTreeViewInfo rule = null;
79      private boolean isInitialized = false;
80      private boolean isNewRule = false;
81      private ReqComponentInfo editedReqCompInfo = null;
82      private Integer internalProgReqID = null;
83      private String originalReqCompNL;
84      private String originalLogicExpression;
85  
86      //   private boolean isLocalDirty = false;
87  	private BlockingTask creatingRuleTask = new BlockingTask("Creating Rule");
88  
89      public ProgramRequirementsManageView(ProgramRequirementsViewController parentController, Enum<?> viewEnum, String name, String modelId) {
90          super(viewEnum, name, modelId);
91          this.parentController = parentController;
92      }
93  
94      @Override
95      public void beforeShow(final Callback<Boolean> onReadyCallback) {
96  
97          retrieveAndSetupReqCompTypes(); //TODO cache it for each statement type?
98          if (!isInitialized) {
99              setupHandlers();
100             draw();
101             isInitialized = true;
102         }
103 
104         onReadyCallback.exec(true);
105     }
106 
107     private void setupHandlers() {
108         editReqCompWidget.setReqCompConfirmButtonClickCallback(actionButtonClickedReqCompCallback);
109         editReqCompWidget.setNewReqCompSelectedCallbackCallback(newReqCompSelectedCallbackCallback);
110         editReqCompWidget.setRetrieveCompositionTemplateCallback(retrieveCompositionTemplateCallback);
111         editReqCompWidget.setRetrieveFieldsMetadataCallback(retrieveFieldsMetadataCallback);
112         editReqCompWidget.setRetrieveCustomWidgetCallback(retrieveCustomWidgetCallback);
113         ruleManageWidget.setReqCompEditButtonClickCallback(editReqCompCallback);
114     }
115 
116     private void draw() {
117 
118         remove(layout);
119         layout.clear();
120 
121         //STEP 1
122         SectionTitle title = SectionTitle.generateH3Title(ProgramProperties.get().programRequirements_manageViewPageStep1Title());
123         title.setStyleName("KS-Program-Requirements-Manage-Step-header1");  //make the header orange
124         layout.add(title);
125 
126         layout.add(editReqCompWidget);
127 
128         //STEP 2
129         title = SectionTitle.generateH3Title(ProgramProperties.get().programRequirements_manageViewPageStep2Title());
130         title.setStyleName("KS-Program-Requirements-Manage-Step-header2");  //make the header orange
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-Program-Requirements-Save-Button");
148         actionCancelButtons.addCallback(new Callback<ButtonEnumerations.ButtonEnum>(){
149              @Override
150             public void exec(final ButtonEnumerations.ButtonEnum result) {
151                 parentController.getView(ProgramRequirementsViewController.ProgramRequirementsViews.PREVIEW, new Callback<View>(){
152                     @Override
153                     public void exec(View preview) {
154                         //update rules data model and summary view
155                         if ((result == ButtonEnumerations.SaveCancelEnum.SAVE) && isDirty()) {
156                             ProgramRequirementInfo affectedRule = ((ProgramRequirementsSummaryView) preview).getRules().updateRules(getRuleTree(), internalProgReqID, isNewRule());
157                             ((ProgramRequirementsSummaryView) preview).updateRequirementWidgets(affectedRule);                            
158                         }
159                         isDirty = false;
160                         parentController.showView(ProgramRequirementsViewController.ProgramRequirementsViews.PREVIEW);
161                     }
162                 });
163              }
164         });
165         addWidget(actionCancelButtons);
166     }
167 
168     // called by requirement display widget when user wants to edit or add a sub-rule
169     public void setRuleTree(StatementTreeViewInfo stmtTreeInfo, boolean newRuleFlag, Integer internalProgReqID) {
170 
171         if (!isInitialized) {
172             editReqCompWidget = new ReqCompEditWidget(ProgramRequirementsSummaryView.NEW_REQ_COMP_ID);
173             ruleManageWidget = new RuleManageWidget();
174             ruleManageWidget.setReqCompEditButtonClickCallback(editReqCompCallback);
175             ruleManageWidget.setRuleChangedButtonClickCallback(ruleChangedCallback);
176         }
177 
178         this.internalProgReqID = internalProgReqID;
179         editedReqCompInfo = null;
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(ProgramRequirementsSummaryView.NEW_STMT_TREE_ID + Integer.toString(ProgramRequirementsSummaryView.tempStmtTreeID++));
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                             statementRpcServiceAsync.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                                     statementRpcServiceAsync.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 Integer getInternalProgReqID() {
472         return internalProgReqID;
473     }
474 }