Java tutorial
/** * Copyright 2010 The Kuali Foundation Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.kuali.student.common.ui.client.mvc; import com.google.gwt.core.client.GWT; import com.google.gwt.event.shared.GwtEvent.Type; import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; import org.kuali.student.common.ui.client.application.KSAsyncCallback; import org.kuali.student.common.ui.client.application.ViewContext; import org.kuali.student.common.ui.client.configurable.mvc.views.SectionView; import org.kuali.student.common.ui.client.mvc.breadcrumb.BreadcrumbSupport; import org.kuali.student.common.ui.client.mvc.history.HistoryManager; import org.kuali.student.common.ui.client.mvc.history.HistorySupport; import org.kuali.student.common.ui.client.mvc.history.NavigationEvent; import org.kuali.student.common.ui.client.reporting.ReportExport; import org.kuali.student.common.ui.client.security.AuthorizationCallback; import org.kuali.student.common.ui.client.security.RequiresAuthorization; import org.kuali.student.common.ui.client.service.GwtExportRpcService; import org.kuali.student.common.ui.client.service.GwtExportRpcServiceAsync; import org.kuali.student.common.ui.client.util.ExportElement; import org.kuali.student.common.ui.client.util.ExportUtils; import org.kuali.student.common.ui.client.widgets.progress.BlockingTask; import org.kuali.student.common.ui.client.widgets.progress.KSBlockingProgressIndicator; import org.kuali.student.r1.common.assembly.data.Data; import java.util.*; /** * Abstract Controller composite. Provides basic controller operations, and defines abstract methods that a composite must * implement in order to be a controller. * * @author Kuali Student Team */ public abstract class Controller extends Composite implements HistorySupport, BreadcrumbSupport, ReportExport { public static final Callback<Boolean> NO_OP_CALLBACK = new Callback<Boolean>() { @Override public void exec(Boolean result) { // do nothing } }; protected Controller parentController = null; private View currentView = null; private Enum<?> currentViewEnum = null; private String defaultModelId = null; protected ViewContext context = new ViewContext(); private final Map<String, ModelProvider<? extends Model>> models = new HashMap<String, ModelProvider<? extends Model>>(); private boolean fireNavEvents = true; private HandlerManager applicationEventHandlers = new HandlerManager(this); private GwtExportRpcServiceAsync reportExportRpcService = GWT.create(GwtExportRpcService.class); protected Controller() { } /** * Simple Version of showView, no callback * @param <V> * view enum type * @param viewType * enum value representing the view to show */ public <V extends Enum<?>> void showView(final V viewType) { this.showView(viewType, NO_OP_CALLBACK); } /** * Directs the controller to display the specified view. The parameter must be an enum value, based on an enum defined in * the controller implementation. For example, a "Search" controller might have an enumeration of: <code> * public enum SearchViews { * SIMPLE_SEARCH, * ADVANCED_SEARCH, * SEARCH_RESULTS * } * </code> The implementing class must define a getView(V viewType) method that will cast the generic enum to the view * specific enum. * * @param <V> * view enum type * @param viewType * enum value representing the view to show * @param onReadyCallback the callback to invoke when the method has completed execution * @return false if the current view cancels the operation */ public <V extends Enum<?>> void showView(final V viewType, final Callback<Boolean> onReadyCallback) { GWT.log("showView " + viewType.toString(), null); getView(viewType, new Callback<View>() { @Override public void exec(View result) { View view = result; if (view == null) { onReadyCallback.exec(false); //throw new ControllerException("View not registered: " + viewType.toString()); } beginShowView(view, viewType, onReadyCallback); } }, null); } protected <V extends Enum<?>> void beginShowView(final View view, final V viewType, final Callback<Boolean> onReadyCallback) { beforeViewChange(viewType, new Callback<Boolean>() { @Override public void exec(Boolean result) { if (result) { boolean requiresAuthz = (view instanceof RequiresAuthorization) && ((RequiresAuthorization) view).isAuthorizationRequired(); if (requiresAuthz) { ViewContext currView = getViewContext(); if (getViewContext() != null && getViewContext().getPermissionTypeGwt() != null && viewType != null) GWT.log("Checking permission type '" + getViewContext().getPermissionTypeGwt().getPermissionTemplateName() + "' for viewType '" + viewType.toString() + "'", null); //A callback is required if async rpc call is required for authz check ((RequiresAuthorization) view).checkAuthorization(new AuthorizationCallback() { public void isAuthorized() { finalizeShowView(view, viewType, onReadyCallback); } public void isNotAuthorized(String msg) { Window.alert(msg); onReadyCallback.exec(false); } }); } else { GWT.log("Not Requiring Auth.", null); finalizeShowView(view, viewType, onReadyCallback); } } else { onReadyCallback.exec(false); } } }); } protected <V extends Enum<?>> void finalizeShowView(final View view, final V viewType, final Callback<Boolean> onReadyCallback) { if (((currentView == null) || currentView.beforeHide()) && view != null) { view.beforeShow(new Callback<Boolean>() { @Override public void exec(Boolean result) { if (!result) { GWT.log("showView: beforeShow yielded false " + viewType, null); onReadyCallback.exec(false); } else { if (currentView != null) { hideView(currentView); } currentViewEnum = viewType; currentView = view; GWT.log("renderView " + viewType.toString(), null); if (fireNavEvents) { fireNavigationEvent(); } renderView(view); onReadyCallback.exec(true); } } }); } else { onReadyCallback.exec(false); GWT.log("Current view canceled hide action", null); } } protected void fireNavigationEvent() { //DeferredCommand.addCommand(new Command() { // @Override //public void execute() { fireApplicationEvent(new NavigationEvent(Controller.this)); //} //}); } /** * Returns the currently displayed view * * @return the currently displayed view */ public View getCurrentView() { return currentView; } public Enum<?> getCurrentViewEnum() { return currentViewEnum; } public void setCurrentViewEnum(Enum<?> currentViewEnum) { this.currentViewEnum = currentViewEnum; } /** * Sets the controller's parent controller. In most cases, this can be omitted as the controller will be automatically * detected via the DOM in cases where it is not specified. The only time that the controller needs to be manually set is * in cases where the logical controller hierarchy differs from the physical DOM hierarchy. For example, if a nested * controller is rendered in a PopupPanel, then the parent controller must be set manually using this method * * @param controller * the parent controller */ public void setParentController(Controller controller) { parentController = controller; } /** * Returns the parent controller. If the current parent controller is not set, then the controller will attempt to * automatically locate the parent controller via the DOM. * * @return */ public Controller getParentController() { if (parentController == null) { parentController = Controller.findController(this); } return parentController; } /** * Attempts to find the parent controller of a given widget via the DOM * * @param w * the widget for which to find the parent controller * @return the controller, or null if not found */ public static Controller findController(Widget w) { Controller result = null; while (true) { w = w.getParent(); if (w == null) { break; } else if (w instanceof Controller) { result = (Controller) w; break; } else if (w instanceof View) { // this is in the event that a parent/child relationship is broken by a view being rendered in a lightbox, // etc result = ((View) w).getController(); break; } } return result; } /** * Called by child views and controllers to request a model reference. By default it delegates calls to the parent * controller if one is found. Override this method to declare a model local to the controller. Always make sure to * delegate the call to the superclass if the requested type is not one which is defined locally. For example: <code> * * @Override * @SuppressWarnings("unchecked") public void requestModel(Class<? extends Idable> modelType, ModelRequestCallback * callback) { if (modelType.equals(Address.class)) { callback.onModelReady(addresses); } * else { super.requestModel(modelType, callback); } } </code> * @param modelType * @param callback */ @SuppressWarnings("unchecked") public void requestModel(final Class modelType, final ModelRequestCallback callback) { requestModel((modelType == null) ? null : modelType.getName(), callback); } @SuppressWarnings("unchecked") public void requestModel(final String modelId, final ModelRequestCallback callback) { String id = (modelId == null) ? defaultModelId : modelId; ModelProvider<? extends Model> p = models.get(id); if (p != null) { p.requestModel(callback); } else if (getParentController() != null) { parentController.requestModel(modelId, callback); } else { if (callback != null) { callback.onRequestFail(new RuntimeException("The requested model was not found: " + modelId)); } } } @SuppressWarnings("rawtypes") public void requestModel(final ModelRequestCallback callback) { requestModel((String) null, callback); } public <T extends Model> void registerModel(String modelId, ModelProvider<T> provider) { models.put(modelId, provider); } public String getDefaultModelId() { return defaultModelId; } public void setDefaultModelId(String defaultModelId) { this.defaultModelId = defaultModelId; } /** * Registers an application eventhandler. The controller will try to propagate "unchecked" handlers to the parent * controller if a parent controller exists. This method can be overridden to handle unchecked locally if they are fired * locally. * * @param type * @param handler * @return */ @SuppressWarnings("unchecked") public HandlerRegistration addApplicationEventHandler(Type type, ApplicationEventHandler handler) { if ((handler instanceof UncheckedApplicationEventHandler) && (getParentController() != null)) { return parentController.addApplicationEventHandler(type, handler); } return applicationEventHandlers.addHandler(type, handler); } /** * Fires an application event. * * @param event */ @SuppressWarnings("unchecked") public void fireApplicationEvent(ApplicationEvent event) { // TODO this logic needs to be reworked a bit... if an unchecked event has been bound locally, do we want to still // fire it externally as well? if ((event instanceof UncheckedApplicationEvent) && (getParentController() != null)) { parentController.fireApplicationEvent(event); } // dispatch to local "checked" handlers, and to any unchecked handlers that have been bound to local applicationEventHandlers.fireEvent(event); } /** * Must be implemented by the subclass to render the view. * * @param view */ protected abstract void renderView(View view); /** * Must be implemented by the subclass to hide the view. * * @param view */ protected abstract void hideView(View view); /** * Returns the view associated with the specified enum value. See showView(V viewType) above for a full description * defaults to the abstract get view method unless overridden * @param <V> * @param viewType * @param callback * @param tokenMap optionally passed in token map if you need tokens from the history manager */ protected <V extends Enum<?>> void getView(V viewType, Callback<View> callback, Map<String, String> tokenMap) { getView(viewType, callback); } /** * Returns the view associated with the specified enum value. See showView(V viewType) above for a full description * * @param <V> * @param viewType * @return */ protected abstract <V extends Enum<?>> void getView(V viewType, Callback<View> callback); /** * If a controller which extends this class must perform some action or check before a view * is changed, then override this method. Do not call super() in the override, as it will * allow the view to continue to change. * @param okToChangeCallback */ public void beforeViewChange(Enum<?> viewChangingTo, Callback<Boolean> okToChangeCallback) { okToChangeCallback.exec(true); } /** * Shows the default view. Must be implemented by subclass, in order to define the default view. */ public abstract void showDefaultView(Callback<Boolean> onReadyCallback); public abstract Enum<?> getViewEnumValue(String enumValue); /** * This particular implementation appends to the history stack the name of the current view shown by * this controller and view context (in string format) to that historyStack and passes the stack to * be processed to the currentView. * @see org.kuali.student.common.ui.client.mvc.history.HistorySupport#collectHistory(java.lang.String) */ @Override public String collectHistory(String historyStack) { String token = getHistoryToken(); historyStack = historyStack + "/" + token; if (currentView != null) { String tempHistoryStack = historyStack; historyStack = currentView.collectHistory(historyStack); //Sanity check, if collectHistory returns null or empty string, restore if (historyStack == null) { historyStack = tempHistoryStack; } else if (historyStack != null && historyStack.isEmpty()) { historyStack = tempHistoryStack; } } return historyStack; } protected String getHistoryToken() { String historyToken = ""; if (currentViewEnum != null) { historyToken = currentViewEnum.toString(); if (currentView != null && currentView instanceof Controller && ((Controller) currentView).getViewContext() != null) { ViewContext context = ((Controller) currentView).getViewContext(); historyToken = HistoryManager.appendContext(historyToken, context); } } return historyToken; } /** * The onHistoryEvent implementation in controller reads the history stack it receives and determines * if the next token/view to be processed is a controller, if it is, it hands off the rest of the history stack * to that controller after showing it. Otherwise, it shows the view * and allows that view to perform any onHistoryEvent actions it may need to take. * <br><br>For example the historyStack /HOME/CURRICULUM_HOME/COURSE_PROPOSAL would start at the root controller, * and hand it off to the home controller, then the curriculum home controller, then the course proposal controller * and stop there. Along the way each of those controller would show themselves visually in the UI, * if they contain any layout (some do not). * * @see org.kuali.student.common.ui.client.mvc.history.HistorySupport#onHistoryEvent(java.lang.String) */ @Override public void onHistoryEvent(String historyStack) { final String nextHistoryStack = HistoryManager.nextHistoryStack(historyStack); String[] tokens = HistoryManager.splitHistoryStack(nextHistoryStack); if (tokens.length >= 1 && tokens[0] != null && !tokens[0].isEmpty()) { final Map<String, String> tokenMap = HistoryManager.getTokenMap(tokens[0]); //TODO add some automatic view context setting here, get and set String viewEnumString = tokenMap.get("view"); if (viewEnumString != null) { final Enum<?> viewEnum = getViewEnumValue(viewEnumString); if (viewEnum != null) { getView(viewEnum, new Callback<View>() { @Override public void exec(View result) { View theView = result; boolean sameContext = true; if (theView instanceof Controller) { ViewContext newContext = new ViewContext(); Iterator<String> tokenIt = tokenMap.keySet().iterator(); while (tokenIt.hasNext()) { String key = tokenIt.next(); if (key.equals(ViewContext.ID_ATR)) { newContext.setId(tokenMap.get(ViewContext.ID_ATR)); } else if (key.equals(ViewContext.ID_TYPE_ATR)) { newContext.setIdType(tokenMap.get(ViewContext.ID_TYPE_ATR)); } //do not add view attribute from the token map to the context else if (!key.equals("view")) { newContext.setAttribute(key, tokenMap.get(key)); } } ViewContext viewContext = ((Controller) theView).getViewContext(); if (viewContext.compareTo(newContext) != 0) { ((Controller) theView).setViewContext(newContext); sameContext = false; } } if (currentViewEnum == null || !viewEnum.equals(currentViewEnum) || !sameContext) { beginShowView(theView, viewEnum, new Callback<Boolean>() { @Override public void exec(Boolean result) { if (result) { currentView.onHistoryEvent(nextHistoryStack); } } }); } else if (currentView != null) { currentView.onHistoryEvent(nextHistoryStack); } } }, tokenMap); } } } else { this.showDefaultView(new Callback<Boolean>() { @Override public void exec(Boolean result) { if (result) { currentView.onHistoryEvent(nextHistoryStack); } } }); } } /** * Sets the view context. This is important for determining the permission for seeing views under * this controllers scope, what the id and id type of the model the controller handles are defined here. * Additional attributes that the controller and it's views need to know about are also defined in the * viewContext. * @param viewContext */ public void setViewContext(ViewContext viewContext) { this.context = viewContext; } public ViewContext getViewContext() { return this.context; } public void resetCurrentView() { currentView = null; } /** * * This method implement the "Generic Export" of a windows content to Jasper based on the format the user selected. * This method can be overwritten on a subclass to do specific export to the specific view * * @see org.kuali.student.common.ui.client.reporting.ReportExport#doReportExport(java.util.List, String format, String reportTitle) */ @Override public void doReportExport(List<ExportElement> exportElements, final String format, final String reportTitle) { // Service call... final BlockingTask loadDataTask = new BlockingTask("Generating Export File"); DataModel dataModel = getExportDataModel(); Data modelDataObject = null; if (dataModel != null) { modelDataObject = dataModel.getRoot(); } // we want to show that something is happening while the files are generated. KSBlockingProgressIndicator.addTask(loadDataTask); reportExportRpcService.reportExport(exportElements, modelDataObject, getExportTemplateName(), format, reportTitle, new KSAsyncCallback<String>() { @Override public void onSuccess(String result) { // On success get documentID back from GWT Servlet// // We need to get the base url and strip the gwt module name . String baseUrl = GWT.getHostPageBaseURL(); baseUrl = baseUrl.replaceFirst(GWT.getModuleName() + "/", ""); KSBlockingProgressIndicator.removeTask(loadDataTask); Window.open(baseUrl + "exportDownloadHTTPServlet?exportId=" + result + "&format=" + format, "", ""); } @Override public void handleFailure(Throwable caught) { KSBlockingProgressIndicator.removeTask(loadDataTask); super.handleFailure(caught); } }); } // TODO Nina ??? Do we want to keep this seen in the light of the exportElements parameter @Override public DataModel getExportDataModel() { return null; } /** * * @see org.kuali.student.common.ui.client.reporting.ReportExport#getExportTemplateName() */ @Override public String getExportTemplateName() { return exportTemplateName; } @Override public List<ExportElement> getExportElementsFromView() { String viewName = null; View currentView = this.getCurrentView(); if (currentView != null) { ArrayList<ExportElement> exportElements = null; if (currentView != null && currentView instanceof SectionView) { viewName = currentView.getName(); exportElements = ExportUtils.getExportElementsFromView((SectionView) currentView, exportElements, viewName, "Sectionname"); return exportElements; } } return null; } }