/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2013 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.server.ngclient; import java.awt.Rectangle; import java.beans.PropertyChangeEvent; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.mozilla.javascript.Context; import org.mozilla.javascript.NativeJavaObject; import org.sablo.websocket.CurrentWindow; import com.servoy.j2db.BasicFormController; import com.servoy.j2db.BasicFormManager; import com.servoy.j2db.ClientState; import com.servoy.j2db.IBasicMainContainer; import com.servoy.j2db.IFormController; import com.servoy.j2db.IModeManager; import com.servoy.j2db.IServiceProvider; import com.servoy.j2db.J2DBGlobals; import com.servoy.j2db.Messages; import com.servoy.j2db.persistence.FlattenedForm; import com.servoy.j2db.persistence.Form; import com.servoy.j2db.persistence.IRepository; import com.servoy.j2db.persistence.ScriptMethod; import com.servoy.j2db.persistence.Solution; import com.servoy.j2db.persistence.SolutionMetaData; import com.servoy.j2db.scripting.InstanceJavaMembers; import com.servoy.j2db.scripting.JSWindow; import com.servoy.j2db.scripting.RuntimeWindow; import com.servoy.j2db.scripting.SolutionScope; import com.servoy.j2db.server.ngclient.component.WebFormController; import com.servoy.j2db.server.ngclient.eventthread.NGClientWebsocketSessionWindows; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.Pair; import com.servoy.j2db.util.ScopesUtils; import com.servoy.j2db.util.Utils; /** * @author jcompagner * */ public class NGFormManager extends BasicFormManager implements INGFormManager { public static final String DEFAULT_DIALOG_NAME = "dialog"; //$NON-NLS-1$ protected final ConcurrentMap<String, IWebFormController> createdFormControllers; // formName -> FormController private Form loginForm; /** * @param application */ public NGFormManager(INGApplication application) { super(application); this.createdFormControllers = new ConcurrentHashMap<>(); } public final INGApplication getApplication() { return (INGApplication)application; } @Override public IFormController getCachedFormController(String formName) { return createdFormControllers.get(formName); } @Override public List<IFormController> getCachedFormControllers() { return new ArrayList<IFormController>(createdFormControllers.values()); } public List<IFormController> getCachedFormControllers(Form form) { ArrayList<IFormController> al = new ArrayList<IFormController>(); for (IFormController controller : createdFormControllers.values()) { if (controller.getForm().getName().equals(form.getName())) { al.add(controller); } else if (controller.getForm() instanceof FlattenedForm) { List<Form> formHierarchy = application.getFlattenedSolution().getFormHierarchy(controller.getForm()); if (formHierarchy.contains(form)) { al.add(controller); } } } return al; } public void makeSolutionSettings(Solution s) { Solution solution = s; Iterator<Form> e = application.getFlattenedSolution().getForms(true); // add all forms first, they may be referred to in the login form Form first = application.getFlattenedSolution().getForm(solution.getFirstFormID()); boolean firstFormCanBeInstantiated = application.getFlattenedSolution().formCanBeInstantiated(first); while (e.hasNext()) { Form form = e.next(); if (application.getFlattenedSolution().formCanBeInstantiated(form)) { if (!firstFormCanBeInstantiated) first = form; firstFormCanBeInstantiated = true; } // add anyway, the form may be used in scripting addForm(form, form.equals(first)); } if (firstFormCanBeInstantiated) { application.getModeManager().setMode(IModeManager.EDIT_MODE);//start in browse mode } if (solution.getLoginFormID() > 0 && solution.getMustAuthenticate() && application.getUserUID() == null) { Form login = application.getFlattenedSolution().getForm(solution.getLoginFormID()); if (application.getFlattenedSolution().formCanBeInstantiated(login) && loginForm == null) { loginForm = login;//must set the login form early so its even correct if onload of login form is called showFormInMainPanel(login.getName()); return; //stop and recall this method from security.login(...)! } } IBasicMainContainer currentContainer = getCurrentContainer(); if (solution.getLoginFormID() > 0 && solution.getMustAuthenticate() && application.getUserUID() != null && loginForm != null) { if (currentContainer.getController() != null && loginForm.getName().equals(currentContainer.getController().getForm().getName())) { currentContainer.setController(null); } loginForm = null;//clear and continue } ScriptMethod sm = application.getFlattenedSolution().getScriptMethod(solution.getOnOpenMethodID()); Object[] solutionOpenMethodArgs = null; String preferedSolutionMethodName = ((ClientState)application).getPreferedSolutionMethodNameToCall(); if (preferedSolutionMethodName == null && ((ClientState)application).getPreferedSolutionMethodArguments() != null) { solutionOpenMethodArgs = ((ClientState)application).getPreferedSolutionMethodArguments(); } if (sm != null) { try { application.getScriptEngine().getScopesScope().executeGlobalFunction(sm.getScopeName(), sm.getName(), Utils.arrayMerge(solutionOpenMethodArgs, Utils.parseJSExpressions(solution.getInstanceMethodArguments("onOpenMethodID"))), false, false); if (application.getSolution() == null) return; } catch (Exception e1) { application.reportError(Messages.getString("servoy.formManager.error.ExecutingOpenSolutionMethod", new Object[] { sm.getName() }), e1); //$NON-NLS-1$ } } if (first != null && getCurrentForm() == null) { showFormInMainPanel(first.getName()); //we only set if the solution startup did not yet show a form already } if (preferedSolutionMethodName != null && application.getFlattenedSolution().isMainSolutionLoaded()) { try { Object[] args = ((ClientState)application).getPreferedSolutionMethodArguments(); // avoid stack overflows when an execute method URL is used to open the solution, and that method does call JSSecurity login ((ClientState)application).resetPreferedSolutionMethodNameToCall(); Pair<String, String> scope = ScopesUtils.getVariableScope(preferedSolutionMethodName); Object result = application.getScriptEngine().getScopesScope().executeGlobalFunction(scope.getLeft(), scope.getRight(), args, false, false); if (application.getSolution().getSolutionType() == SolutionMetaData.AUTHENTICATOR) { application.getRuntimeProperties().put(IServiceProvider.RT_OPEN_METHOD_RESULT, result); } } catch (Exception e1) { application.reportError( Messages.getString("servoy.formManager.error.ExecutingOpenSolutionMethod", new Object[] { preferedSolutionMethodName }), //$NON-NLS-1$ e1); } } } @Override public IWebFormController getForm(String name) { IWebFormController fc = createdFormControllers.get(name); if (fc == null) { fc = leaseFormPanel(name); } return fc; } public void removeFormController(BasicFormController fp) { createdFormControllers.remove(fp.getName()); } @Override public IWebFormController leaseFormPanel(String formName) { if (formName == null) return null; String name = formName; IWebFormController fp = createdFormControllers.get(name); if (fp == null) { Form f = possibleForms.get(formName); if (f == null) return null; try { application.blockGUI(application.getI18NMessage("servoy.formManager.loadingForm") + formName); //$NON-NLS-1$ f = application.getFlattenedSolution().getFlattenedForm(f); fp = new WebFormController((INGApplication)application, f, name); createdFormControllers.put(fp.getName(), fp); fp.init(); fp.setView(fp.getView()); fp.executeOnLoadMethod(); } finally { application.releaseGUI(); } } return fp; } public void setCurrentControllerJS(IWebFormController currentController) { SolutionScope ss = application.getScriptEngine().getSolutionScope(); Context.enter(); try { ss.put("currentcontroller", ss, //$NON-NLS-1$ new NativeJavaObject(ss, currentController.initForJSUsage(), new InstanceJavaMembers(ss, com.servoy.j2db.BasicFormController.JSForm.class))); } finally { Context.exit(); } } @Override public void clearLoginForm() { if (application.getSolution().getMustAuthenticate()) makeSolutionSettings(application.getSolution()); } @Override public IWebFormController getCurrentForm() { return getCurrentMainShowingFormController(); } @Override public IWebFormController getCurrentMainShowingFormController() { NGRuntimeWindow currentWindow = ((INGApplication)application).getRuntimeWindowManager().getCurrentWindow(); if (currentWindow == null) return null; return currentWindow.getController(); } @Override public void init() { // ignore } @Override public void flushCachedItems() { // ignore } @Override public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); if ("solution".equals(name)) //$NON-NLS-1$ { final Solution s = (Solution)evt.getNewValue(); Runnable run = new Runnable() { @Override public void run() { boolean isReload = s != null; destroySolutionSettings(isReload);//must run on same thread if (isReload) { makeSolutionSettings(s); } } }; if (CurrentWindow.exists()) run.run(); else CurrentWindow.runForWindow(new NGClientWebsocketSessionWindows(getApplication().getWebsocketSession()), run); } else if ("mode".equals(name)) //$NON-NLS-1$ { int oldmode = ((Integer)evt.getOldValue()).intValue(); int newmode = ((Integer)evt.getNewValue()).intValue(); IFormController fp = getCurrentMainShowingFormController(); if (oldmode == IModeManager.FIND_MODE || newmode == IModeManager.FIND_MODE) { fp.setMode(newmode); } } } protected void destroySolutionSettings(boolean reload) { loginForm = null; for (IFormController controller : createdFormControllers.values()) { controller.destroy(); } createdFormControllers.clear(); possibleForms.clear(); // cleanup windows (containers) NGRuntimeWindowManager wm = ((INGApplication)application).getRuntimeWindowManager(); wm.destroy(reload); } @Override public IFormController showFormInMainPanel(String name) { return showFormInCurrentContainer(name); } @Override public IFormController showFormInContainer(String formName, IBasicMainContainer container, String title, boolean closeAll, String dialogName) { if (loginForm != null && loginForm.getName() != formName) { return null;//not allowed to leave here...or show anything else than login form } if (formName == null) throw new IllegalArgumentException(application.getI18NMessage("servoy.formManager.error.SettingVoidForm")); //$NON-NLS-1$ IFormController currentMainShowingForm = container.getController(); if (currentMainShowingForm != null && formName.equals(currentMainShowingForm.getName())) return leaseFormPanel(currentMainShowingForm.getName()); final Form f = possibleForms.get(formName); if (f == null) { return null; } try { int access = application.getFlattenedSolution().getSecurityAccess(f.getUUID()); if (access != -1) { boolean b_visible = ((access & IRepository.VIEWABLE) != 0); if (!b_visible) { application.reportWarningInStatus(application.getI18NMessage("servoy.formManager.warningAccessForm")); //$NON-NLS-1$ return null; } } //handle old panel if (currentMainShowingForm != null) { //leave forms in browse mode // TODO can this be set if notifyVisible returns false (current form is being kept) if (application.getModeManager().getMode() != IModeManager.EDIT_MODE) { application.getModeManager().setMode(IModeManager.EDIT_MODE); } IWebFormController fp = leaseFormPanel(currentMainShowingForm.getName()); if (fp != null) { List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>(); boolean ok = fp.notifyVisible(false, invokeLaterRunnables); Utils.invokeLater(application, invokeLaterRunnables); // solution closed in onhide method of previous form? if (application.getSolution() == null) return null; if (!ok) { return fp; } } } //set main IFormController tmpForm = currentMainShowingForm; final IWebFormController fp = leaseFormPanel(formName); currentMainShowingForm = fp; if (fp != null) { setCurrentControllerJS(fp); //add to history getHistory(container).add(fp.getName()); // test if solution is closed in the onload method. if (application.getSolution() == null) return null; container.setController(fp); //show panel as main List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>(); fp.notifyVisible(true, invokeLaterRunnables); String titleText = title; if (titleText == null) titleText = f.getTitleText(); if (titleText == null || titleText.equals("")) titleText = fp.getName(); //$NON-NLS-1$ if (NO_TITLE_TEXT.equals(titleText)) titleText = ""; //$NON-NLS-1$ container.setTitle(titleText); fp.getFormUI().setParentWindowName(container.getContainerName()); // if (isNewUser) // { // final IMainContainer showContainer = currentContainer; // currentContainer.showBlankPanel();//to overcome paint problem in swing... // invokeLaterRunnables.add(new Runnable() // { // public void run() // { // // only call show if it is still the right form. // FormController currentController = showContainer.getController(); // if (currentController != null && fp.getName().equals(currentController.getName())) // { // showContainer.show(fp.getName()); // application.getRuntimeWindowManager().setCurrentWindowName(dialogName); // } // } // }); // } // else // { // currentContainer.show(fp.getName()); // application.getRuntimeWindowManager().setCurrentWindowName(dialogName); // } // invokeLaterRunnables.add(title_focus); Utils.invokeLater(application, invokeLaterRunnables); } else { container.setController(null); } J2DBGlobals.firePropertyChange(this, "form", tmpForm, currentMainShowingForm); //$NON-NLS-1$ return fp; } catch (Exception e) { Debug.error(e); } return null; } @Override public IFormController showFormInCurrentContainer(String formName) { return showFormInContainer(formName, getCurrentContainer(), null, false, getCurrentContainer().getContainerName()); } @Override public void showFormInDialog(String formName, Rectangle bounds, String title, boolean resizeble, boolean showTextToolbar, boolean closeAll, boolean modal, String windowName) { boolean legacyV3Behavior = false; // first window is modal, second reuses same dialog if (windowName == null) { windowName = DEFAULT_DIALOG_NAME; legacyV3Behavior = true; } RuntimeWindow thisWindow = application.getRuntimeWindowManager().getWindow(windowName); if (thisWindow != null && thisWindow.getType() != JSWindow.DIALOG && thisWindow.getType() != JSWindow.MODAL_DIALOG) { thisWindow.hide(true); // make sure it is closed before reference to it is lost thisWindow.destroy(); thisWindow = null; } if (thisWindow == null) { thisWindow = application.getRuntimeWindowManager().createWindow(windowName, modal ? JSWindow.MODAL_DIALOG : JSWindow.DIALOG, null); } thisWindow.setInitialBounds(bounds.x, bounds.y, bounds.width, bounds.height); thisWindow.showTextToolbar(showTextToolbar); thisWindow.setTitle(title); thisWindow.setResizable(resizeble); thisWindow.oldShow(formName, closeAll, legacyV3Behavior); } @Override public void showFormInFrame(String formName, Rectangle bounds, String windowTitle, boolean resizeble, boolean showTextToolbar, String windowName) { if (windowName == null) { windowName = DEFAULT_DIALOG_NAME; } RuntimeWindow thisWindow = application.getRuntimeWindowManager().getWindow(windowName); if (thisWindow != null && thisWindow.getType() != JSWindow.WINDOW) { thisWindow.hide(true); // make sure it's closed before reference to it is lost thisWindow.destroy(); thisWindow = null; } if (thisWindow == null) { thisWindow = application.getRuntimeWindowManager().createWindow(windowName, JSWindow.WINDOW, null); } thisWindow.setInitialBounds(bounds.x, bounds.y, bounds.width, bounds.height); thisWindow.showTextToolbar(showTextToolbar); thisWindow.setTitle(windowTitle); thisWindow.setResizable(resizeble); thisWindow.oldShow(formName, true, false); // last two params are really not relevant for windows } @Override public IBasicMainContainer getCurrentContainer() { return ((INGApplication)application).getRuntimeWindowManager().getCurrentWindow(); } @Override public History getHistory(IBasicMainContainer container) { IBasicMainContainer c = container; if (c == null) { IBasicMainContainer currentContainer = getCurrentContainer(); if (currentContainer != null) { c = currentContainer; } else { c = getMainContainer(null); } } return c.getHistory(); } @Override public IBasicMainContainer getMainContainer(String windowName) { return ((INGApplication)application).getRuntimeWindowManager().getWindow(windowName); } @Override public boolean isCurrentTheMainContainer() { // main containers should be the once without a parent (so tabs in browser but no dialogs) // so setTitle should also just set it on the current main window of the active endpoint return ((NGRuntimeWindow)getCurrentContainer()).getParent() == null; } @Override public IWebFormController getFormAndSetCurrentWindow(String formName) { IWebFormController form = getForm(formName); if (form == null) throw new RuntimeException("form not found for formname:" + formName); String windowName = form.getFormUI().getParentWindowName(); if (windowName != null) { application.getRuntimeWindowManager().setCurrentWindowName(windowName); setCurrentControllerJS(getCurrentForm()); } else { Debug.log("should the form " + formName + " have a window name, window very likely already closed?"); // throw new RuntimeException("window not set for form:" + formName + " (" + form + ")"); } return form; } }