Coverage Report - org.kuali.student.common.ui.client.mvc.Controller
 
Classes in this File Line Coverage Branch Coverage Complexity
Controller
0%
0/108
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.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
                                                 PermissionType permType = (tempContext != null) ? tempContext.getPermissionType() : null;
 133  0
                                                 if (permType != null) {
 134  0
                                                         GWT.log("Checking permission type '" + permType.getPermissionTemplateName() + "' for view '" + view.toString() + "'", null);
 135  
                                                     //A callback is required if async rpc call is required for authz check
 136  0
                                                         ((RequiresAuthorization)view).checkAuthorization(permType, new AuthorizationCallback(){
 137  
                                                                         public void isAuthorized() {
 138  0
                                                                                 finalizeShowView(view, viewType, onReadyCallback);
 139  0
                                                                         }
 140  
                                 
 141  
                                                                         public void isNotAuthorized(String msg) {
 142  0
                                                                                 Window.alert(msg);
 143  0
                                                                                 onReadyCallback.exec(false);                                        
 144  0
                                                                         }                        
 145  
                                                         });
 146  
                                                 }
 147  
                                                 else {
 148  0
                                                         GWT.log("Cannot find PermissionType for view '" + view.toString() + "' which requires authorization", null);
 149  0
                                                     finalizeShowView(view, viewType, onReadyCallback);
 150  
                                                 }
 151  0
                                         } else {
 152  0
                                                     GWT.log("Not Requiring Auth.", null);
 153  0
                                                 finalizeShowView(view, viewType, onReadyCallback);
 154  
                                         }
 155  0
                                 }
 156  
                                 else{
 157  0
                                         onReadyCallback.exec(false);
 158  
                                 }
 159  
                                 
 160  0
                         }
 161  
                 });
 162  0
     }
 163  
     
 164  
     private <V extends Enum<?>> void finalizeShowView(final View view, final V viewType, final Callback<Boolean> onReadyCallback){
 165  0
         if ((currentView == null) || currentView.beforeHide()) {
 166  0
                         view.beforeShow(new Callback<Boolean>() {
 167  
                                 @Override
 168  
                                 public void exec(Boolean result) {
 169  0
                                         if (!result) {
 170  0
                                                 GWT.log("showView: beforeShow yielded false " + viewType, null);
 171  0
                                         onReadyCallback.exec(false);
 172  
                                         } else {
 173  0
                                         if (currentView != null) {
 174  0
                                         hideView(currentView);
 175  
                                     }
 176  
                                     
 177  0
                                     currentViewEnum = viewType;
 178  0
                                     currentView = view;
 179  0
                                     GWT.log("renderView " + viewType.toString(), null);
 180  0
                                     if(fireNavEvents){
 181  0
                                             fireNavigationEvent();
 182  
                                     }
 183  0
                                     renderView(view);
 184  0
                                         onReadyCallback.exec(true);
 185  
                                         
 186  
                                         }
 187  0
                                 }
 188  
                         });
 189  
         } else {
 190  0
                 onReadyCallback.exec(false);
 191  0
             GWT.log("Current view canceled hide action", null);
 192  
         }            
 193  0
     }
 194  
 
 195  
     protected void fireNavigationEvent() {
 196  
         //DeferredCommand.addCommand(new Command() {
 197  
            // @Override
 198  
             //public void execute() {
 199  0
                 fireApplicationEvent(new NavigationEvent(Controller.this));
 200  
             //}
 201  
         //});
 202  0
     }
 203  
     
 204  
     /**
 205  
      * Returns the currently displayed view
 206  
      * 
 207  
      * @return the currently displayed view
 208  
      */
 209  
     public View getCurrentView() {
 210  0
         return currentView;
 211  
     }
 212  
     
 213  
     public Enum<?> getCurrentViewEnum() {
 214  0
         return currentViewEnum;
 215  
     }
 216  
 
 217  
     public void setCurrentViewEnum(Enum<?> currentViewEnum) {
 218  0
         this.currentViewEnum = currentViewEnum;
 219  0
     }
 220  
 
 221  
     /**
 222  
      * Sets the controller's parent controller. In most cases, this can be omitted as the controller will be automatically
 223  
      * detected via the DOM in cases where it is not specified. The only time that the controller needs to be manually set is
 224  
      * in cases where the logical controller hierarchy differs from the physical DOM hierarchy. For example, if a nested
 225  
      * controller is rendered in a PopupPanel, then the parent controller must be set manually using this method
 226  
      * 
 227  
      * @param controller
 228  
      *            the parent controller
 229  
      */
 230  
     public void setParentController(Controller controller) {
 231  0
         parentController = controller;
 232  0
     }
 233  
 
 234  
     /**
 235  
      * Returns the parent controller. If the current parent controller is not set, then the controller will attempt to
 236  
      * automatically locate the parent controller via the DOM.
 237  
      * 
 238  
      * @return
 239  
      */
 240  
     public Controller getParentController() {
 241  0
         if (parentController == null) {
 242  0
             parentController = Controller.findController(this);
 243  
         }
 244  0
         return parentController;
 245  
     }
 246  
 
 247  
     /**
 248  
      * Attempts to find the parent controller of a given widget via the DOM
 249  
      * 
 250  
      * @param w
 251  
      *            the widget for which to find the parent controller
 252  
      * @return the controller, or null if not found
 253  
      */
 254  
     public static Controller findController(Widget w) {
 255  0
         Controller result = null;
 256  
         while (true) {
 257  0
             w = w.getParent();
 258  0
             if (w == null) {
 259  0
                 break;
 260  0
             } else if (w instanceof Controller) {
 261  0
                 result = (Controller) w;
 262  0
                 break;
 263  0
             } else if (w instanceof View) {
 264  
                 // this is in the event that a parent/child relationship is broken by a view being rendered in a lightbox,
 265  
                 // etc
 266  0
                 result = ((View) w).getController();
 267  0
                 break;
 268  
             }
 269  
         }
 270  0
         return result;
 271  
     }
 272  
 
 273  
     /**
 274  
      * Called by child views and controllers to request a model reference. By default it delegates calls to the parent
 275  
      * controller if one is found. Override this method to declare a model local to the controller. Always make sure to
 276  
      * delegate the call to the superclass if the requested type is not one which is defined locally. For example: <code>
 277  
      * 
 278  
      * @Override
 279  
      * @SuppressWarnings("unchecked") public void requestModel(Class<? extends Idable> modelType, ModelRequestCallback
 280  
      *                                callback) { if (modelType.equals(Address.class)) { callback.onModelReady(addresses); }
 281  
      *                                else { super.requestModel(modelType, callback); } } </code>
 282  
      * @param modelType
 283  
      * @param callback
 284  
      */
 285  
     @SuppressWarnings("unchecked")
 286  
     public void requestModel(final Class modelType, final ModelRequestCallback callback) {
 287  0
         requestModel((modelType == null) ? null : modelType.getName(), callback);
 288  0
     }
 289  
     
 290  
     @SuppressWarnings("unchecked")
 291  
     public void requestModel(final String modelId, final ModelRequestCallback callback) {
 292  0
         String id = (modelId == null) ? defaultModelId : modelId;
 293  
 
 294  0
         ModelProvider<? extends Model> p = models.get(id);
 295  0
         if (p != null) {
 296  0
             p.requestModel(callback);
 297  0
         } else if (getParentController() != null) {
 298  0
             parentController.requestModel(modelId, callback);
 299  
         } else {
 300  0
             if (callback != null) {
 301  0
                 callback.onRequestFail(new RuntimeException("The requested model was not found: " + modelId));
 302  
             }
 303  
         }
 304  0
     }
 305  
 
 306  
     @SuppressWarnings("unchecked")
 307  
     public void requestModel(final ModelRequestCallback callback) {
 308  0
         requestModel((String)null, callback);
 309  0
     }
 310  
     
 311  
     public <T extends Model> void registerModel(String modelId, ModelProvider<T> provider) {
 312  0
         models.put(modelId, provider);
 313  0
     }
 314  
     
 315  
     public String getDefaultModelId() {
 316  0
         return defaultModelId;
 317  
     }
 318  
     public void setDefaultModelId(String defaultModelId) {
 319  0
         this.defaultModelId = defaultModelId;
 320  0
     }
 321  
     
 322  
     /**
 323  
      * Registers an application eventhandler. The controller will try to propagate "unchecked" handlers to the parent
 324  
      * controller if a parent controller exists. This method can be overridden to handle unchecked locally if they are fired
 325  
      * locally.
 326  
      * 
 327  
      * @param type
 328  
      * @param handler
 329  
      * @return
 330  
      */
 331  
     @SuppressWarnings("unchecked")
 332  
     public HandlerRegistration addApplicationEventHandler(Type type, ApplicationEventHandler handler) {
 333  0
         if ((handler instanceof UncheckedApplicationEventHandler) && (getParentController() != null)) {
 334  0
             return parentController.addApplicationEventHandler(type, handler);
 335  
         }
 336  0
         return applicationEventHandlers.addHandler(type, handler);
 337  
     }
 338  
 
 339  
     /**
 340  
      * Fires an application event.
 341  
      * 
 342  
      * @param event
 343  
      */
 344  
     @SuppressWarnings("unchecked")
 345  
     public void fireApplicationEvent(ApplicationEvent event) {
 346  
         // TODO this logic needs to be reworked a bit... if an unchecked event has been bound locally, do we want to still
 347  
         // fire it externally as well?
 348  0
         if ((event instanceof UncheckedApplicationEvent) && (getParentController() != null)) {
 349  0
             parentController.fireApplicationEvent(event);
 350  
         }
 351  
         // dispatch to local "checked" handlers, and to any unchecked handlers that have been bound to local
 352  0
         applicationEventHandlers.fireEvent(event);
 353  
 
 354  0
     }
 355  
 
 356  
     /**
 357  
      * Must be implemented by the subclass to render the view.
 358  
      * 
 359  
      * @param view
 360  
      */
 361  
     protected abstract void renderView(View view);
 362  
 
 363  
     /**
 364  
      * Must be implemented by the subclass to hide the view.
 365  
      * 
 366  
      * @param view
 367  
      */
 368  
     protected abstract void hideView(View view);
 369  
 
 370  
     /**
 371  
      * Returns the view associated with the specified enum value. See showView(V viewType) above for a full description
 372  
      * 
 373  
      * @param <V>
 374  
      * @param viewType
 375  
      * @return
 376  
      */
 377  
     protected abstract <V extends Enum<?>> void getView(V viewType, Callback<View> callback);
 378  
     
 379  
     /**
 380  
      * If a controller which extends this class must perform some action or check before a view
 381  
      * is changed, then override this method.  Do not call super() in the override, as it will
 382  
      * allow the view to continue to change.
 383  
      * @param okToChangeCallback
 384  
      */
 385  
     public void beforeViewChange(Enum<?> viewChangingTo, Callback<Boolean> okToChangeCallback) {
 386  0
             okToChangeCallback.exec(true);
 387  0
     }
 388  
 
 389  
     /**
 390  
      * Shows the default view. Must be implemented by subclass, in order to define the default view.
 391  
      */
 392  
     public abstract void showDefaultView(Callback<Boolean> onReadyCallback);
 393  
     
 394  
     public abstract Enum<?> getViewEnumValue(String enumValue);
 395  
     
 396  
     /**
 397  
      * This particular implementation appends to the history stack the name of the current view shown by
 398  
      * this controller and view context (in string format) to that historyStack and passes the stack to
 399  
      * be processed to the currentView.
 400  
      * @see org.kuali.student.common.ui.client.mvc.history.HistorySupport#collectHistory(java.lang.String)
 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  
     /**
 437  
      * The onHistoryEvent implementation in controller reads the history stack it receives and determines
 438  
      * if the next token/view to be processed is a controller, if it is, it hands off the rest of the history stack
 439  
      * to that controller after showing it.  Otherwise, it shows the view
 440  
      * and allows that view to perform any onHistoryEvent actions it may need to take.
 441  
      * <br><br>For example the historyStack /HOME/CURRICULUM_HOME/COURSE_PROPOSAL would start at the root controller,
 442  
      * and hand it off to the home controller, then the curriculum home controller, then the course proposal controller
 443  
      * and stop there.  Along the way each of those controller would show themselves visually in the UI, 
 444  
      * if they contain any layout (some do not).
 445  
      * 
 446  
      * @see org.kuali.student.common.ui.client.mvc.history.HistorySupport#onHistoryEvent(java.lang.String)
 447  
      */
 448  
     @Override
 449  
     public void onHistoryEvent(String historyStack) {
 450  0
             final String nextHistoryStack = HistoryManager.nextHistoryStack(historyStack);
 451  0
         String[] tokens = HistoryManager.splitHistoryStack(nextHistoryStack);
 452  0
         if (tokens.length >= 1 && tokens[0] != null && !tokens[0].isEmpty()) {
 453  0
             final Map<String, String> tokenMap = HistoryManager.getTokenMap(tokens[0]);
 454  
             //TODO add some automatic view context setting here, get and set
 455  0
             String viewEnumString = tokenMap.get("view");
 456  0
             if (viewEnumString != null) {
 457  0
                 final Enum<?> viewEnum = getViewEnumValue(viewEnumString);
 458  
                 
 459  0
                 if (viewEnum != null) {
 460  0
                         getView(viewEnum, new Callback<View>(){
 461  
 
 462  
                                                 @Override
 463  
                                                 public void exec(View result) {
 464  0
                                                         View theView = result;
 465  0
                                             boolean sameContext = true;
 466  0
                                         if(theView instanceof Controller){
 467  
                                                 
 468  0
                                                 ViewContext newContext = new ViewContext();
 469  0
                                                 Iterator<String> tokenIt = tokenMap.keySet().iterator();
 470  0
                                                 while(tokenIt.hasNext()){
 471  0
                                                         String key = tokenIt.next();
 472  0
                                                         if(key.equals(ViewContext.ID_ATR)){
 473  0
                                                                 newContext.setId(tokenMap.get(ViewContext.ID_ATR));
 474  
                                                         }
 475  0
                                                         else if(key.equals(ViewContext.ID_TYPE_ATR)){
 476  0
                                                                 newContext.setIdType(tokenMap.get(ViewContext.ID_TYPE_ATR));
 477  
                                                         }
 478  
                                                         //do not add view attribute from the token map to the context
 479  0
                                                         else if(!key.equals("view")){
 480  0
                                                                 newContext.setAttribute(key, tokenMap.get(key));
 481  
                                                         }
 482  0
                                                 }
 483  
                                                 
 484  0
                                                 ViewContext viewContext = ((Controller) theView).getViewContext();
 485  0
                                                 if(viewContext.compareTo(newContext) != 0){
 486  0
                                                         ((Controller) theView).setViewContext(newContext);
 487  0
                                                         sameContext = false;
 488  
                                                 }
 489  
                                         }
 490  0
                                     if (currentViewEnum == null || !viewEnum.equals(currentViewEnum) 
 491  
                                                     || !sameContext) {
 492  0
                                         beginShowView(theView, viewEnum, new Callback<Boolean>() {
 493  
                                             @Override
 494  
                                             public void exec(Boolean result) {
 495  0
                                                 if (result) {
 496  0
                                                     currentView.onHistoryEvent(nextHistoryStack);
 497  
                                                 }
 498  0
                                             }
 499  
                                         });
 500  0
                                     } else if (currentView != null) {
 501  0
                                             currentView.onHistoryEvent(nextHistoryStack);
 502  
                                     }
 503  0
                                                 }
 504  
                                         });
 505  
     
 506  
                 }
 507  
             }
 508  0
         }
 509  
         else{
 510  0
                     this.showDefaultView(new Callback<Boolean>(){
 511  
 
 512  
                                 @Override
 513  
                                 public void exec(Boolean result) {
 514  0
                                         if(result){
 515  0
                                                 currentView.onHistoryEvent(nextHistoryStack);
 516  
                                         }
 517  
                                         
 518  0
                                 }
 519  
                         });
 520  
             }
 521  
         
 522  0
     }
 523  
 
 524  
     /**
 525  
      * Sets the view context.  This is important for determining the permission for seeing views under
 526  
      * this controllers scope, what the id and id type of the model the controller handles are defined here.
 527  
      * Additional attributes that the controller and it's views need to know about are also defined in the
 528  
      * viewContext.
 529  
      * @param viewContext
 530  
      */
 531  
     public void setViewContext(ViewContext viewContext){
 532  0
             this.context = viewContext;
 533  0
     }
 534  
 
 535  
     public ViewContext getViewContext() {
 536  0
             return this.context;
 537  
     }
 538  
     
 539  
     public void resetCurrentView(){
 540  0
             currentView = null;
 541  0
     }
 542  
     
 543  
 }