/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 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.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.swing.SwingUtilities; import org.apache.wicket.RequestCycle; import org.apache.wicket.Session; import com.servoy.j2db.FormController; import com.servoy.j2db.IDebugWebClient; import com.servoy.j2db.IDesignerCallback; import com.servoy.j2db.IFormController; import com.servoy.j2db.IFormManagerInternal; import com.servoy.j2db.dataprocessing.IDataServer; 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.persistence.SolutionMetaData; import com.servoy.j2db.scripting.IExecutingEnviroment; import com.servoy.j2db.server.headlessclient.WebClient; import com.servoy.j2db.server.headlessclient.WebClientSession; import com.servoy.j2db.server.headlessclient.WebFormManager; import com.servoy.j2db.server.headlessclient.eventthread.IEventDispatcher; import com.servoy.j2db.server.headlessclient.eventthread.WicketEventDispatcher; import com.servoy.j2db.server.shared.WebCredentials; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.ILogLevel; /** * @author jcompagner * */ public class DebugWebClient extends WebClient implements IDebugWebClient { private SolutionMetaData solution; private final List<Thread> dispatchThreads = new ArrayList<Thread>(3); private final IDesignerCallback designerCallBack; public DebugWebClient(HttpServletRequest req, WebCredentials credentials, String method, Object[] methodArgs, SolutionMetaData solution, IDesignerCallback designerCallBack) throws Exception { super(req, credentials, method, methodArgs, solution != null ? solution.getName() : ""); this.solution = solution; this.designerCallBack = designerCallBack; } public synchronized void addEventDispatchThread() { if (!dispatchThreads.contains(Thread.currentThread())) { dispatchThreads.add(Thread.currentThread()); } } public synchronized void removeEventDispatchThread() { dispatchThreads.remove(Thread.currentThread()); } /** * @see com.servoy.j2db.server.headlessclient.SessionClient#isEventDispatchThread() */ @Override public boolean isEventDispatchThread() { // this has to be in synch with invokelater if (dispatchThreads.size() == 0 || SwingUtilities.isEventDispatchThread() || isShutDown()) { return super.isEventDispatchThread(); } return dispatchThreads.contains(Thread.currentThread()); } @Override protected IFormManagerInternal createFormManager() { return new DebugHeadlessClient.DebugWebFormManager(this, getMainPage()); } /** * @see com.servoy.j2db.server.headlessclient.WebClient#shutDown(boolean) */ @Override public void shutDown(boolean force) { boolean sessionExists = Session.exists() && (RequestCycle.get() != null); if (sessionExists) Session.unset(); // avoid session invalidating in super.shutDown - as the current session might be needed when DebugWC is restarted (by next DWC) super.shutDown(force); if (sessionExists) Session.get(); // null pointers fix when switching between browsers in developer. if (force && session != null) { try { session.invalidate(); } catch (Exception e) { // ignore } } } @Override protected void loadSolution(SolutionMetaData solutionMeta) throws RepositoryException {// set the dispatch thread to this one if not already set. addEventDispatchThread(); // ignore given always load the active. if (getSolution() != null) { closeSolution(true, null); } if (solution != null) { // reset the preferred solution always to this solution. // debug client can't load another. preferredSolutionNameToLoadOnInit = solution.getName(); super.loadSolution(solution); } } public void setCurrent(Solution sol) { solution = (sol == null) ? null : sol.getSolutionMetaData(); } private Form form; private final List<List<IPersist>> changesQueue = Collections.synchronizedList(new ArrayList<List<IPersist>>()); private final List<List<IFormController>> recreateUISet = Collections.synchronizedList(new ArrayList<List<IFormController>>()); private boolean performRefresh() { boolean changed = changesQueue.size() > 0; while (changesQueue.size() > 0) { performRefresh(changesQueue.remove(0)); } if (!changed) changed = recreateUISet.size() > 0; while (recreateUISet.size() > 0) { List<IFormController> lst = recreateUISet.remove(0); for (IFormController fc : lst) { fc.recreateUI(); } } return changed; } private void performRefresh(List<IPersist> changes) { Set<IFormController>[] scopesAndFormsToReload = DebugUtils.getScopesAndFormsToReload(this, changes); for (IFormController controller : scopesAndFormsToReload[0]) { if (controller.getForm() instanceof FlattenedForm) { FlattenedForm ff = (FlattenedForm)controller.getForm(); ff.reload(); } controller.getFormScope().reload(); } if (scopesAndFormsToReload[1].size() > 0) ((WebFormManager)getFormManager()).reload((scopesAndFormsToReload[1]).toArray(new FormController[0])); } public void refreshForI18NChange(boolean recreateForms) { refreshI18NMessages(); if (recreateForms) { List<IFormController> cachedFormControllers = getFormManager().getCachedFormControllers(); recreateUISet.add(cachedFormControllers); } } /** * @param changes */ public void refreshPersists(Collection<IPersist> changes) { changesQueue.add(new ArrayList<IPersist>(changes)); } /** * @see com.servoy.j2db.server.headlessclient.SessionClient#createScriptEngine() */ @Override protected IExecutingEnviroment createScriptEngine() { RemoteDebugScriptEngine engine = new RemoteDebugScriptEngine(this); if (designerCallBack != null) { designerCallBack.addScriptObjects(this, engine.getSolutionScope()); } return engine; } /* * (non-Javadoc) * * @see com.servoy.j2db.server.headlessclient.WebClient#createDispatcher() */ @Override protected IEventDispatcher createDispatcher() { return new WicketEventDispatcher(this) { @Override public void run() { // just add this thread to the dispatchers. addEventDispatchThread(); super.run(); } }; } /** * @see com.servoy.j2db.smart.J2DBClient#output(java.lang.Object) */ @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); } } /** * @see com.servoy.j2db.smart.J2DBClient#reportJSError(java.lang.String, java.lang.Object) */ @Override public void reportJSError(String message, Object detail) { DebugUtils.errorToDebugger(getScriptEngine(), message, detail); super.reportJSError(message, detail); } /** * @see com.servoy.j2db.ClientState#reportError(java.lang.String, java.lang.Object) */ @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); } public void show(Form f) { this.form = f; } public synchronized boolean checkForChanges() { if (getClientInfo() == null) return false; boolean changed = false; SolutionMetaData mainSolutionMetaData = getFlattenedSolution().getMainSolutionMetaData(); if ((mainSolutionMetaData == null && solution != null) || (mainSolutionMetaData != null && !mainSolutionMetaData.getName().equals(solution.getName()))) { try { loadSolution(solution); changed = true; } catch (Exception e) { Debug.error(e); } } if (!changed) { changed = performRefresh(); } if (getSolution() != null && form != null) { getFormManager().showFormInMainPanel(form.getName()); form = null; changed = true; } return changed; } @Override public void onBeginRequest(WebClientSession webClientSession) { if (getSolution() != null) { addEventDispatchThread(); checkForChanges(); synchronized (onBeginRequestLock) { executeEvents(); } } } @Override public void onEndRequest(WebClientSession webClientSession) { super.onEndRequest(webClientSession); removeEventDispatchThread(); } /* * (non-Javadoc) * * @see com.servoy.j2db.ClientState#createDataServer() */ @Override protected IDataServer createDataServer() { IDataServer dataServer = super.createDataServer(); if (dataServer != null) { dataServer = new ProfileDataServer(dataServer); } return dataServer; } private HashMap<Object, Object> changedProperties; private boolean wasLoginSolution; @Override public boolean putClientProperty(Object name, Object val) { if (name != null && changedProperties != null && !changedProperties.containsKey(name)) { changedProperties.put(name, getClientProperty(name)); if (getSolution() != null && getSolution().getSolutionType() == SolutionMetaData.LOGIN_SOLUTION) { wasLoginSolution = true; } } return super.putClientProperty(name, val); } public void onSolutionOpen() { if (changedProperties == null) { changedProperties = new HashMap<Object, Object>(); } else { if (!wasLoginSolution) { Iterator<Map.Entry<Object, Object>> changedPropertiesIte = changedProperties.entrySet().iterator(); Map.Entry<Object, Object> changedEntry; while (changedPropertiesIte.hasNext()) { changedEntry = changedPropertiesIte.next(); super.putClientProperty(changedEntry.getKey(), changedEntry.getValue()); } changedProperties.clear(); } else { wasLoginSolution = false; } } } @Override protected int getSolutionTypeFilter() { return super.getSolutionTypeFilter(); } }