/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2014 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.debug; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import org.sablo.eventthread.WebsocketSessionWindows; import org.sablo.specification.WebObjectSpecification; import org.sablo.specification.WebServiceSpecProvider; import org.sablo.websocket.CurrentWindow; import org.sablo.websocket.IWindow; import com.servoy.j2db.BasicFormController; import com.servoy.j2db.IBasicFormManager; import com.servoy.j2db.IDebugClient; import com.servoy.j2db.IDesignerCallback; import com.servoy.j2db.IFormController; import com.servoy.j2db.dataprocessing.FoundSet; import com.servoy.j2db.persistence.AbstractBase; import com.servoy.j2db.persistence.FlattenedForm; import com.servoy.j2db.persistence.Form; import com.servoy.j2db.persistence.IPersist; import com.servoy.j2db.persistence.RepositoryException; import com.servoy.j2db.persistence.Solution; import com.servoy.j2db.scripting.IExecutingEnviroment; import com.servoy.j2db.scripting.PluginScope; import com.servoy.j2db.server.ngclient.FormElementHelper; import com.servoy.j2db.server.ngclient.INGClientWebsocketSession; import com.servoy.j2db.server.ngclient.INGFormManager; import com.servoy.j2db.server.ngclient.IWebFormController; import com.servoy.j2db.server.ngclient.NGClient; import com.servoy.j2db.server.ngclient.NGFormManager; import com.servoy.j2db.server.ngclient.NGRuntimeWindowManager; import com.servoy.j2db.server.ngclient.WebFormUI; import com.servoy.j2db.server.ngclient.component.WebFormController; import com.servoy.j2db.server.ngclient.eventthread.NGClientWebsocketSessionWindows; import com.servoy.j2db.server.ngclient.scripting.WebServiceScriptable; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.ILogLevel; import com.servoy.j2db.util.ServoyException; import com.servoy.j2db.util.Utils; /** * @author jcompagner * */ public class DebugNGClient extends NGClient implements IDebugClient { private final IDesignerCallback designerCallback; private Solution current; final class DebugNGFormMananger extends NGFormManager implements DebugUtils.DebugUpdateFormSupport { private DebugNGFormMananger(DebugNGClient app) { super(app); } public void updateForm(Form form) { boolean isNew = !possibleForms.containsKey(form.getName()); boolean isDeleted = false; if (!isNew) { isDeleted = !((AbstractBase)form.getParent()).getAllObjectsAsList().contains(form); } updateForm(form, isDeleted); } private void updateForm(Form form, boolean isDeleted) { if (isDeleted) { IFormController tmp = getCachedFormController(form.getName()); if (tmp != null) removeFormController((BasicFormController)tmp); // form was deleted in designer; remove it's controller from cached/already used forms possibleForms.remove(form.getName()); } } } /** * @param webSocketClientEndpoint * @param designerCallback */ public DebugNGClient(INGClientWebsocketSession wsSession, IDesignerCallback designerCallback) throws Exception { super(wsSession); this.designerCallback = designerCallback; getWebsocketSession().registerServerService("developerService", new DeveloperServiceHandler(this)); } @Override public synchronized void shutDown(boolean force) { // shutdown can be called for an older client when opening a new debug client. // then nothing should be done for the current window which is already for the new client instance IWindow currentWindow = null; if (CurrentWindow.exists() && !CurrentWindow.get().getSession().getUuid().equals(getWebsocketSession().getUuid())) { currentWindow = CurrentWindow.set(null); } try { super.shutDown(force); } finally { CurrentWindow.set(currentWindow); } } @Override protected IBasicFormManager createFormManager() { return new DebugNGFormMananger(this); } @Override protected IExecutingEnviroment createScriptEngine() { RemoteDebugScriptEngine engine = new RemoteDebugScriptEngine(this); WebObjectSpecification[] serviceSpecifications = WebServiceSpecProvider.getInstance().getAllWebServiceSpecifications(); PluginScope scope = (PluginScope)engine.getSolutionScope().get("plugins", engine.getSolutionScope()); scope.setLocked(false); for (WebObjectSpecification serviceSpecification : serviceSpecifications) { scope.put(serviceSpecification.getName(), scope, new WebServiceScriptable(this, serviceSpecification, engine.getSolutionScope())); } scope.setLocked(true); if (designerCallback != null) { designerCallback.addScriptObjects(this, engine.getSolutionScope()); } return engine; } @Override public void output(Object msg, int level) { super.output(msg, level); if (level == ILogLevel.WARNING || level == ILogLevel.ERROR) { DebugUtils.errorToDebugger(getScriptEngine(), msg.toString(), null); } else { DebugUtils.stdoutToDebugger(getScriptEngine(), msg); } } @Override public void reportJSError(String message, Object detail) { DebugUtils.errorToDebugger(getScriptEngine(), message, detail); super.reportJSError(message, detail); } @Override public void reportError(String message, Object detail) { DebugUtils.errorToDebugger(getScriptEngine(), message, detail); super.reportError(message, detail); } @Override public void reportJSWarning(String s) { DebugUtils.errorToDebugger(getScriptEngine(), s, null); super.reportJSWarning(s); } @Override public void reportJSInfo(String s) { DebugUtils.stdoutToDebugger(getScriptEngine(), "INFO: " + s); super.reportJSInfo(s); } @Override public void setCurrent(Solution current) { this.current = current; closeSolution(true, null); getWebsocketSession().sendRedirect("/solutions/" + current.getName() + "/index.html"); } @Override public void loadSolution(String solutionName) throws RepositoryException { if (current == null || current.getName().equals(solutionName)) { super.loadSolution(solutionName); } else if (getWebsocketSession() != null) { getWebsocketSession().sendRedirect(current != null ? "/solutions/" + current.getName() + "/index.html" : null); } } @Override public void refreshForI18NChange(boolean recreateForms) { if (isShutDown()) return; refreshI18NMessages(); if (recreateForms) { List<IFormController> cachedFormControllers = getFormManager().getCachedFormControllers(); refreshForms(cachedFormControllers); } } private void refreshForms(Collection<IFormController> forms) { if (forms != null && forms.size() > 0) { FormElementHelper.INSTANCE.reload(); List<IFormController> cachedFormControllers = getFormManager().getCachedFormControllers(); for (IFormController formController : cachedFormControllers) { if (formController.getFormUI() instanceof WebFormUI) { ((WebFormUI)formController.getFormUI()).clearCachedFormElements(); } } List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>(); // should we also use these? for (IFormController controller : forms) { boolean isVisible = controller.isFormVisible(); if (isVisible) controller.notifyVisible(false, invokeLaterRunnables); if (!Utils.stringSafeEquals(controller.getDataSource(), controller.getFormModel().getDataSource())) { // for now we just destroy the form and recreate it with the other datasource; // TODO we just load the shared foundset for that datasource - can we improve this somehow so that the loaded foundset is closer to the current runtime situation of the form? (related tabs etc.) String name = controller.getName(); controller.destroy(); controller = getFormManager().leaseFormPanel(name); FoundSet foundset; try { foundset = (FoundSet)getFoundSetManager().getSharedFoundSet(controller.getDataSource()); foundset.loadAllRecords(); controller.loadRecords(foundset); } catch (ServoyException e) { Debug.error(e); } } else { ((WebFormController)controller).initFormUI(); } if (isVisible) controller.notifyVisible(true, invokeLaterRunnables); } WebsocketSessionWindows allendpoints = new NGClientWebsocketSessionWindows(getWebsocketSession()); allendpoints.executeAsyncServiceCall(NGRuntimeWindowManager.WINDOW_SERVICE, "reload", null, null); try { allendpoints.flush(); } catch (IOException e) { reportError("error sending changes to the client", e); } } } @Override public void refreshPersists(Collection<IPersist> changes) { if (isShutDown()) return; Set<IFormController>[] scopesAndFormsToReload = DebugUtils.getScopesAndFormsToReload(this, changes); refreshForms(scopesAndFormsToReload[1]); for (IFormController controller : scopesAndFormsToReload[0]) { if (controller.getForm() instanceof FlattenedForm) { FlattenedForm ff = (FlattenedForm)controller.getForm(); ff.reload(); } controller.getFormScope().reload(); } } /** * @param form */ public void show(Form form) { // TODO Auto-generated method stub } @Override protected void showInfoPanel() { //ignore } @Override public boolean isInDesigner() { return false; } IDesignerCallback getDesignerCallback() { return designerCallback; } }