Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
Controller |
|
| 2.268292682926829;2.268 | ||||
Controller$1 |
|
| 2.268292682926829;2.268 | ||||
Controller$2 |
|
| 2.268292682926829;2.268 | ||||
Controller$3 |
|
| 2.268292682926829;2.268 | ||||
Controller$3$1 |
|
| 2.268292682926829;2.268 | ||||
Controller$4 |
|
| 2.268292682926829;2.268 | ||||
Controller$5 |
|
| 2.268292682926829;2.268 | ||||
Controller$5$1 |
|
| 2.268292682926829;2.268 | ||||
Controller$6 |
|
| 2.268292682926829;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 | } |