Coverage Report - org.kuali.student.common.ui.client.mvc.Controller
 
Classes in this File Line Coverage Branch Coverage Complexity
Controller
0%
0/107
0%
0/56
2.268
Controller$1
0%
0/2
N/A
2.268
Controller$2
0%
0/6
0%
0/2
2.268
Controller$3
0%
0/20
0%
0/14
2.268
Controller$3$1
0%
0/6
N/A
2.268
Controller$4
0%
0/14
0%
0/6
2.268
Controller$5
0%
0/24
0%
0/20
2.268
Controller$5$1
0%
0/4
0%
0/2
2.268
Controller$6
0%
0/4
0%
0/2
2.268
 
 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.common.ui.client.mvc;
 17  
 
 18  
 import java.util.HashMap;
 19  
 import java.util.Iterator;
 20  
 import java.util.Map;
 21  
 
 22  
 import org.kuali.student.common.rice.authorization.PermissionType;
 23  
 import org.kuali.student.common.ui.client.application.ViewContext;
 24  
 import org.kuali.student.common.ui.client.configurable.mvc.LayoutController;
 25  
 import org.kuali.student.common.ui.client.mvc.breadcrumb.BreadcrumbSupport;
 26  
 import org.kuali.student.common.ui.client.mvc.history.HistoryManager;
 27  
 import org.kuali.student.common.ui.client.mvc.history.HistorySupport;
 28  
 import org.kuali.student.common.ui.client.mvc.history.NavigationEvent;
 29  
 import org.kuali.student.common.ui.client.security.AuthorizationCallback;
 30  
 import org.kuali.student.common.ui.client.security.RequiresAuthorization;
 31  
 
 32  
 import com.google.gwt.core.client.GWT;
 33  
 import com.google.gwt.event.shared.GwtEvent.Type;
 34  
 import com.google.gwt.event.shared.HandlerManager;
 35  
 import com.google.gwt.event.shared.HandlerRegistration;
 36  
 import com.google.gwt.user.client.Window;
 37  
 import com.google.gwt.user.client.ui.Composite;
 38  
 import com.google.gwt.user.client.ui.Widget;
 39  
 
 40  
 /**
 41  
  * Abstract Controller composite. Provides basic controller operations, and defines abstract methods that a composite must
 42  
  * implement in order to be a controller.
 43  
  * 
 44  
  * @author Kuali Student Team
 45  
  */
 46  0
 public abstract class Controller extends Composite implements HistorySupport, BreadcrumbSupport{
 47  0
         public static final Callback<Boolean> NO_OP_CALLBACK = new Callback<Boolean>() {
 48  
                 @Override
 49  
                 public void exec(Boolean result) {
 50  
                         // do nothing
 51  0
                 }
 52  
         };
 53  
         
 54  0
     protected Controller parentController = null;
 55  0
     private View currentView = null;
 56  0
     private Enum<?> currentViewEnum = null;
 57  0
     private String defaultModelId = null;
 58  0
     protected ViewContext context = new ViewContext();
 59  0
     private final Map<String, ModelProvider<? extends Model>> models = new HashMap<String, ModelProvider<? extends Model>>();
 60  0
     private boolean fireNavEvents = true;
 61  0
     private HandlerManager applicationEventHandlers = new HandlerManager(this);
 62  
     
 63  0
     protected Controller() {
 64  0
     }
 65  
     
 66  
     /**
 67  
      * Simple Version of showView, no callback
 68  
      * @param <V>
 69  
      *                         view enum type
 70  
      * @param viewType
 71  
      *                         enum value representing the view to show
 72  
      */
 73  
     public <V extends Enum<?>> void showView(final V viewType){
 74  0
             this.showView(viewType, NO_OP_CALLBACK);
 75  0
     }
 76  
     
 77  
 
 78  
     
 79  
     /**
 80  
      * Directs the controller to display the specified view. The parameter must be an enum value, based on an enum defined in
 81  
      * the controller implementation. For example, a "Search" controller might have an enumeration of: <code>
 82  
      *  public enum SearchViews {
 83  
      *      SIMPLE_SEARCH,
 84  
      *      ADVANCED_SEARCH,
 85  
      *      SEARCH_RESULTS
 86  
      *  }
 87  
      * </code> The implementing class must define a getView(V viewType) method that will cast the generic enum to the view
 88  
      * specific enum.
 89  
      * 
 90  
      * @param <V>
 91  
      *            view enum type
 92  
      * @param viewType
 93  
      *            enum value representing the view to show
 94  
      * @param onReadyCallback the callback to invoke when the method has completed execution
 95  
      * @return false if the current view cancels the operation
 96  
      */
 97  
     public <V extends Enum<?>> void showView(final V viewType, final Callback<Boolean> onReadyCallback) {
 98  0
         GWT.log("showView " + viewType.toString(), null);
 99  0
         getView(viewType, new Callback<View>(){
 100  
 
 101  
                         @Override
 102  
                         public void exec(View result) {
 103  0
                                 View view = result;
 104  0
                                 if (view == null) {
 105  0
                                 onReadyCallback.exec(false);
 106  
                             //throw new ControllerException("View not registered: " + viewType.toString());
 107  
                         }
 108  0
                         beginShowView(view, viewType, onReadyCallback);
 109  
                                 
 110  0
                         }});
 111  0
     }
 112  
     
 113  
     private <V extends Enum<?>> void beginShowView(final View view, final V viewType, final Callback<Boolean> onReadyCallback){
 114  0
             beforeViewChange(viewType, new Callback<Boolean>(){
 115  
 
 116  
                         @Override
 117  
                         public void exec(Boolean result) {
 118  0
                                 if(result){
 119  0
                                          boolean requiresAuthz = (view instanceof RequiresAuthorization) && ((RequiresAuthorization)view).isAuthorizationRequired(); 
 120  
                                                 
 121  0
                                         if (requiresAuthz){
 122  0
                                                 ViewContext tempContext = new ViewContext();
 123  0
                                                 if(view instanceof LayoutController){
 124  0
                                                         tempContext = ((LayoutController) view).getViewContext();
 125  
                                                 }                 
 126  
                                                 else{
 127  0
                                                         tempContext = view.getController().getViewContext();
 128  
                                                 }
 129  
                                                 
 130  0
                                                 PermissionType permType = (tempContext != null) ? tempContext.getPermissionType() : null;
 131  0
                                                 if (permType != null) {
 132  0
                                                         GWT.log("Checking permission type '" + permType.getPermissionTemplateName() + "' for view '" + view.toString() + "'", null);
 133  
                                                     //A callback is required if async rpc call is required for authz check
 134  0
                                                         ((RequiresAuthorization)view).checkAuthorization(permType, new AuthorizationCallback(){
 135  
                                                                         public void isAuthorized() {
 136  0
                                                                                 finalizeShowView(view, viewType, onReadyCallback);
 137  0
                                                                         }
 138  
                                 
 139  
                                                                         public void isNotAuthorized(String msg) {
 140  0
                                                                                 Window.alert(msg);
 141  0
                                                                                 onReadyCallback.exec(false);                                        
 142  0
                                                                         }                        
 143  
                                                         });
 144  
                                                 }
 145  
                                                 else {
 146  0
                                                         GWT.log("Cannot find PermissionType for view '" + view.toString() + "' which requires authorization", null);
 147  0
                                                     finalizeShowView(view, viewType, onReadyCallback);
 148  
                                                 }
 149  0
                                         } else {
 150  0
                                                     GWT.log("Not Requiring Auth.", null);
 151  0
                                                 finalizeShowView(view, viewType, onReadyCallback);
 152  
                                         }
 153  0
                                 }
 154  
                                 else{
 155  0
                                         onReadyCallback.exec(false);
 156  
                                 }
 157  
                                 
 158  0
                         }
 159  
                 });
 160  0
     }
 161  
     
 162  
     private <V extends Enum<?>> void finalizeShowView(final View view, final V viewType, final Callback<Boolean> onReadyCallback){
 163  0
         if ((currentView == null) || currentView.beforeHide()) {
 164  0
                         view.beforeShow(new Callback<Boolean>() {
 165  
                                 @Override
 166  
                                 public void exec(Boolean result) {
 167  0
                                         if (!result) {
 168  0
                                                 GWT.log("showView: beforeShow yielded false " + viewType, null);
 169  0
                                         onReadyCallback.exec(false);
 170  
                                         } else {
 171  0
                                         if (currentView != null) {
 172  0
                                         hideView(currentView);
 173  
                                     }
 174  
                                     
 175  0
                                     currentViewEnum = viewType;
 176  0
                                     currentView = view;
 177  0
                                     GWT.log("renderView " + viewType.toString(), null);
 178  0
                                     if(fireNavEvents){
 179  0
                                             fireNavigationEvent();
 180  
                                     }
 181  0
                                     renderView(view);
 182  0
                                         onReadyCallback.exec(true);
 183  
                                         
 184  
                                         }
 185  0
                                 }
 186  
                         });
 187  
         } else {
 188  0
                 onReadyCallback.exec(false);
 189  0
             GWT.log("Current view canceled hide action", null);
 190  
         }            
 191  0
     }
 192  
 
 193  
     protected void fireNavigationEvent() {
 194  
         //DeferredCommand.addCommand(new Command() {
 195  
            // @Override
 196  
             //public void execute() {
 197  0
                 fireApplicationEvent(new NavigationEvent(Controller.this));
 198  
             //}
 199  
         //});
 200  0
     }
 201  
     
 202  
     /**
 203  
      * Returns the currently displayed view
 204  
      * 
 205  
      * @return the currently displayed view
 206  
      */
 207  
     public View getCurrentView() {
 208  0
         return currentView;
 209  
     }
 210  
     
 211  
     public Enum<?> getCurrentViewEnum() {
 212  0
         return currentViewEnum;
 213  
     }
 214  
 
 215  
     public void setCurrentViewEnum(Enum<?> currentViewEnum) {
 216  0
         this.currentViewEnum = currentViewEnum;
 217  0
     }
 218  
 
 219  
     /**
 220  
      * Sets the controller's parent controller. In most cases, this can be omitted as the controller will be automatically
 221  
      * detected via the DOM in cases where it is not specified. The only time that the controller needs to be manually set is
 222  
      * in cases where the logical controller hierarchy differs from the physical DOM hierarchy. For example, if a nested
 223  
      * controller is rendered in a PopupPanel, then the parent controller must be set manually using this method
 224  
      * 
 225  
      * @param controller
 226  
      *            the parent controller
 227  
      */
 228  
     public void setParentController(Controller controller) {
 229  0
         parentController = controller;
 230  0
     }
 231  
 
 232  
     /**
 233  
      * Returns the parent controller. If the current parent controller is not set, then the controller will attempt to
 234  
      * automatically locate the parent controller via the DOM.
 235  
      * 
 236  
      * @return
 237  
      */
 238  
     public Controller getParentController() {
 239  0
         if (parentController == null) {
 240  0
             parentController = Controller.findController(this);
 241  
         }
 242  0
         return parentController;
 243  
     }
 244  
 
 245  
     /**
 246  
      * Attempts to find the parent controller of a given widget via the DOM
 247  
      * 
 248  
      * @param w
 249  
      *            the widget for which to find the parent controller
 250  
      * @return the controller, or null if not found
 251  
      */
 252  
     public static Controller findController(Widget w) {
 253  0
         Controller result = null;
 254  
         while (true) {
 255  0
             w = w.getParent();
 256  0
             if (w == null) {
 257  0
                 break;
 258  0
             } else if (w instanceof Controller) {
 259  0
                 result = (Controller) w;
 260  0
                 break;
 261  0
             } else if (w instanceof View) {
 262  
                 // this is in the event that a parent/child relationship is broken by a view being rendered in a lightbox,
 263  
                 // etc
 264  0
                 result = ((View) w).getController();
 265  0
                 break;
 266  
             }
 267  
         }
 268  0
         return result;
 269  
     }
 270  
 
 271  
     /**
 272  
      * Called by child views and controllers to request a model reference. By default it delegates calls to the parent
 273  
      * controller if one is found. Override this method to declare a model local to the controller. Always make sure to
 274  
      * delegate the call to the superclass if the requested type is not one which is defined locally. For example: <code>
 275  
      * 
 276  
      * @Override
 277  
      * @SuppressWarnings("unchecked") public void requestModel(Class<? extends Idable> modelType, ModelRequestCallback
 278  
      *                                callback) { if (modelType.equals(Address.class)) { callback.onModelReady(addresses); }
 279  
      *                                else { super.requestModel(modelType, callback); } } </code>
 280  
      * @param modelType
 281  
      * @param callback
 282  
      */
 283  
     @SuppressWarnings("unchecked")
 284  
     public void requestModel(final Class modelType, final ModelRequestCallback callback) {
 285  0
         requestModel((modelType == null) ? null : modelType.getName(), callback);
 286  0
     }
 287  
     
 288  
     @SuppressWarnings("unchecked")
 289  
     public void requestModel(final String modelId, final ModelRequestCallback callback) {
 290  0
         String id = (modelId == null) ? defaultModelId : modelId;
 291  
 
 292  0
         ModelProvider<? extends Model> p = models.get(id);
 293  0
         if (p != null) {
 294  0
             p.requestModel(callback);
 295  0
         } else if (getParentController() != null) {
 296  0
             parentController.requestModel(modelId, callback);
 297  
         } else {
 298  0
             if (callback != null) {
 299  0
                 callback.onRequestFail(new RuntimeException("The requested model was not found: " + modelId));
 300  
             }
 301  
         }
 302  0
     }
 303  
 
 304  
     @SuppressWarnings("unchecked")
 305  
     public void requestModel(final ModelRequestCallback callback) {
 306  0
         requestModel((String)null, callback);
 307  0
     }
 308  
 
 309  
     public <T extends Model> void registerModel(String modelId, ModelProvider<T> provider) {
 310  0
         models.put(modelId, provider);
 311  0
     }
 312  
     
 313  
     public String getDefaultModelId() {
 314  0
         return defaultModelId;
 315  
     }
 316  
     public void setDefaultModelId(String defaultModelId) {
 317  0
         this.defaultModelId = defaultModelId;
 318  0
     }
 319  
     
 320  
     /**
 321  
      * Registers an application eventhandler. The controller will try to propagate "unchecked" handlers to the parent
 322  
      * controller if a parent controller exists. This method can be overridden to handle unchecked locally if they are fired
 323  
      * locally.
 324  
      * 
 325  
      * @param type
 326  
      * @param handler
 327  
      * @return
 328  
      */
 329  
     @SuppressWarnings("unchecked")
 330  
     public HandlerRegistration addApplicationEventHandler(Type type, ApplicationEventHandler handler) {
 331  0
         if ((handler instanceof UncheckedApplicationEventHandler) && (getParentController() != null)) {
 332  0
             return parentController.addApplicationEventHandler(type, handler);
 333  
         }
 334  0
         return applicationEventHandlers.addHandler(type, handler);
 335  
     }
 336  
 
 337  
     /**
 338  
      * Fires an application event.
 339  
      * 
 340  
      * @param event
 341  
      */
 342  
     @SuppressWarnings("unchecked")
 343  
     public void fireApplicationEvent(ApplicationEvent event) {
 344  
         // TODO this logic needs to be reworked a bit... if an unchecked event has been bound locally, do we want to still
 345  
         // fire it externally as well?
 346  0
         if ((event instanceof UncheckedApplicationEvent) && (getParentController() != null)) {
 347  0
             parentController.fireApplicationEvent(event);
 348  
         }
 349  
         // dispatch to local "checked" handlers, and to any unchecked handlers that have been bound to local
 350  0
         applicationEventHandlers.fireEvent(event);
 351  
 
 352  0
     }
 353  
 
 354  
     /**
 355  
      * Must be implemented by the subclass to render the view.
 356  
      * 
 357  
      * @param view
 358  
      */
 359  
     protected abstract void renderView(View view);
 360  
 
 361  
     /**
 362  
      * Must be implemented by the subclass to hide the view.
 363  
      * 
 364  
      * @param view
 365  
      */
 366  
     protected abstract void hideView(View view);
 367  
 
 368  
     /**
 369  
      * Returns the view associated with the specified enum value. See showView(V viewType) above for a full description
 370  
      * 
 371  
      * @param <V>
 372  
      * @param viewType
 373  
      * @return
 374  
      */
 375  
     protected abstract <V extends Enum<?>> void getView(V viewType, Callback<View> callback);
 376  
     
 377  
     /**
 378  
      * If a controller which extends this class must perform some action or check before a view
 379  
      * is changed, then override this method.  Do not call super() in the override, as it will
 380  
      * allow the view to continue to change.
 381  
      * @param okToChangeCallback
 382  
      */
 383  
     public void beforeViewChange(Enum<?> viewChangingTo, Callback<Boolean> okToChangeCallback) {
 384  0
             okToChangeCallback.exec(true);
 385  0
     }
 386  
 
 387  
     /**
 388  
      * Shows the default view. Must be implemented by subclass, in order to define the default view.
 389  
      */
 390  
     public abstract void showDefaultView(Callback<Boolean> onReadyCallback);
 391  
     
 392  
     public abstract Enum<?> getViewEnumValue(String enumValue);
 393  
     
 394  
     /**
 395  
      * This particular implementation appends to the history stack the name of the current view shown by
 396  
      * this controller and view context (in string format) to that historyStack and passes the stack to
 397  
      * be processed to the currentView.
 398  
      * @see org.kuali.student.common.ui.client.mvc.history.HistorySupport#collectHistory(java.lang.String)
 399  
      */
 400  
     @Override
 401  
     public String collectHistory(String historyStack) {
 402  0
             String token = getHistoryToken();
 403  0
             historyStack = historyStack + "/" + token;
 404  
             
 405  0
             if(currentView != null){
 406  0
                     String tempHistoryStack = historyStack;
 407  0
                     historyStack = currentView.collectHistory(historyStack);
 408  
                     
 409  
                     //Sanity check, if collectHistory returns null or empty string, restore
 410  0
                     if(historyStack == null){
 411  0
                             historyStack = tempHistoryStack;
 412  
                     }
 413  0
                     else if(historyStack != null && historyStack.isEmpty()){
 414  0
                             historyStack = tempHistoryStack;
 415  
                     }
 416  
             }
 417  0
             return historyStack;
 418  
     }
 419  
     
 420  
     protected String getHistoryToken() {
 421  0
             String historyToken = "";
 422  0
         if (currentViewEnum != null) {
 423  0
             historyToken = currentViewEnum.toString();
 424  0
             if(currentView != null && currentView instanceof Controller 
 425  
                             && ((Controller)currentView).getViewContext() != null){
 426  0
                     ViewContext context = ((Controller) currentView).getViewContext();
 427  0
                     historyToken = HistoryManager.appendContext(historyToken, context);
 428  
             }
 429  
              
 430  
         }
 431  0
         return historyToken;
 432  
     }
 433  
 
 434  
     /**
 435  
      * The onHistoryEvent implementation in controller reads the history stack it receives and determines
 436  
      * if the next token/view to be processed is a controller, if it is, it hands off the rest of the history stack
 437  
      * to that controller after showing it.  Otherwise, it shows the view
 438  
      * and allows that view to perform any onHistoryEvent actions it may need to take.
 439  
      * <br><br>For example the historyStack /HOME/CURRICULUM_HOME/COURSE_PROPOSAL would start at the root controller,
 440  
      * and hand it off to the home controller, then the curriculum home controller, then the course proposal controller
 441  
      * and stop there.  Along the way each of those controller would show themselves visually in the UI, 
 442  
      * if they contain any layout (some do not).
 443  
      * 
 444  
      * @see org.kuali.student.common.ui.client.mvc.history.HistorySupport#onHistoryEvent(java.lang.String)
 445  
      */
 446  
     @Override
 447  
     public void onHistoryEvent(String historyStack) {
 448  0
             final String nextHistoryStack = HistoryManager.nextHistoryStack(historyStack);
 449  0
         String[] tokens = HistoryManager.splitHistoryStack(nextHistoryStack);
 450  0
         if (tokens.length >= 1 && tokens[0] != null && !tokens[0].isEmpty()) {
 451  0
             final Map<String, String> tokenMap = HistoryManager.getTokenMap(tokens[0]);
 452  
             //TODO add some automatic view context setting here, get and set
 453  0
             String viewEnumString = tokenMap.get("view");
 454  0
             if (viewEnumString != null) {
 455  0
                 final Enum<?> viewEnum = getViewEnumValue(viewEnumString);
 456  
                 
 457  0
                 if (viewEnum != null) {
 458  0
                         getView(viewEnum, new Callback<View>(){
 459  
 
 460  
                                                 @Override
 461  
                                                 public void exec(View result) {
 462  0
                                                         View theView = result;
 463  0
                                             boolean sameContext = true;
 464  0
                                         if(theView instanceof Controller){
 465  
                                                 
 466  0
                                                 ViewContext newContext = new ViewContext();
 467  0
                                                 Iterator<String> tokenIt = tokenMap.keySet().iterator();
 468  0
                                                 while(tokenIt.hasNext()){
 469  0
                                                         String key = tokenIt.next();
 470  0
                                                         if(key.equals(ViewContext.ID_ATR)){
 471  0
                                                                 newContext.setId(tokenMap.get(ViewContext.ID_ATR));
 472  
                                                         }
 473  0
                                                         else if(key.equals(ViewContext.ID_TYPE_ATR)){
 474  0
                                                                 newContext.setIdType(tokenMap.get(ViewContext.ID_TYPE_ATR));
 475  
                                                         }
 476  
                                                         //do not add view attribute from the token map to the context
 477  0
                                                         else if(!key.equals("view")){
 478  0
                                                                 newContext.setAttribute(key, tokenMap.get(key));
 479  
                                                         }
 480  0
                                                 }
 481  
                                                 
 482  0
                                                 ViewContext viewContext = ((Controller) theView).getViewContext();
 483  0
                                                 if(viewContext.compareTo(newContext) != 0){
 484  0
                                                         ((Controller) theView).setViewContext(newContext);
 485  0
                                                         sameContext = false;
 486  
                                                 }
 487  
                                         }
 488  0
                                     if (currentViewEnum == null || !viewEnum.equals(currentViewEnum) 
 489  
                                                     || !sameContext) {
 490  0
                                         beginShowView(theView, viewEnum, new Callback<Boolean>() {
 491  
                                             @Override
 492  
                                             public void exec(Boolean result) {
 493  0
                                                 if (result) {
 494  0
                                                     currentView.onHistoryEvent(nextHistoryStack);
 495  
                                                 }
 496  0
                                             }
 497  
                                         });
 498  0
                                     } else if (currentView != null) {
 499  0
                                             currentView.onHistoryEvent(nextHistoryStack);
 500  
                                     }
 501  0
                                                 }
 502  
                                         });
 503  
     
 504  
                 }
 505  
             }
 506  0
         }
 507  
         else{
 508  0
                     this.showDefaultView(new Callback<Boolean>(){
 509  
 
 510  
                                 @Override
 511  
                                 public void exec(Boolean result) {
 512  0
                                         if(result){
 513  0
                                                 currentView.onHistoryEvent(nextHistoryStack);
 514  
                                         }
 515  
                                         
 516  0
                                 }
 517  
                         });
 518  
             }
 519  
         
 520  0
     }
 521  
 
 522  
     /**
 523  
      * Sets the view context.  This is important for determining the permission for seeing views under
 524  
      * this controllers scope, what the id and id type of the model the controller handles are defined here.
 525  
      * Additional attributes that the controller and it's views need to know about are also defined in the
 526  
      * viewContext.
 527  
      * @param viewContext
 528  
      */
 529  
     public void setViewContext(ViewContext viewContext){
 530  0
             this.context = viewContext;
 531  0
     }
 532  
 
 533  
     public ViewContext getViewContext() {
 534  0
             return this.context;
 535  
     }
 536  
     
 537  
     public void resetCurrentView(){
 538  0
             currentView = null;
 539  0
     }
 540  
     
 541  
 }