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.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 | } |