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