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