/*
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.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.Frame;
import java.awt.KeyboardFocusManager;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.DefaultPersistenceDelegate;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager.LookAndFeelInfo;
import org.mozilla.javascript.RhinoException;
import com.servoy.j2db.Credentials;
import com.servoy.j2db.FormController;
import com.servoy.j2db.FormManager;
import com.servoy.j2db.FormWindow;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.IBeanManager;
import com.servoy.j2db.IBrowserLauncher;
import com.servoy.j2db.IDebugJ2DBClient;
import com.servoy.j2db.IDesignerCallback;
import com.servoy.j2db.IFormController;
import com.servoy.j2db.IFormManagerInternal;
import com.servoy.j2db.ILAFManager;
import com.servoy.j2db.IMainContainer;
import com.servoy.j2db.ISmartClientApplication;
import com.servoy.j2db.Messages;
import com.servoy.j2db.RuntimeWindowManager;
import com.servoy.j2db.component.ComponentFactory;
import com.servoy.j2db.dataprocessing.FoundSet;
import com.servoy.j2db.dataprocessing.IDataServer;
import com.servoy.j2db.dataprocessing.IFoundSetInternal;
import com.servoy.j2db.gui.FormDialog;
import com.servoy.j2db.gui.LoginDialog;
import com.servoy.j2db.persistence.AbstractBase;
import com.servoy.j2db.persistence.FlattenedForm;
import com.servoy.j2db.persistence.Form;
import com.servoy.j2db.persistence.IActiveSolutionHandler;
import com.servoy.j2db.persistence.IPersist;
import com.servoy.j2db.persistence.RepositoryException;
import com.servoy.j2db.persistence.ScriptMethod;
import com.servoy.j2db.persistence.Solution;
import com.servoy.j2db.persistence.SolutionMetaData;
import com.servoy.j2db.scripting.FunctionDefinition;
import com.servoy.j2db.scripting.IExecutingEnviroment;
import com.servoy.j2db.scripting.IScriptSupport;
import com.servoy.j2db.scripting.RuntimeWindow;
import com.servoy.j2db.server.shared.ApplicationServerRegistry;
import com.servoy.j2db.server.shared.IApplicationServer;
import com.servoy.j2db.server.shared.IApplicationServerAccess;
import com.servoy.j2db.server.shared.IDebugApplicationServer;
import com.servoy.j2db.server.shared.RemoteActiveSolutionHandler;
import com.servoy.j2db.smart.FormFrame;
import com.servoy.j2db.smart.J2DBClient;
import com.servoy.j2db.smart.LoadingUIEffects;
import com.servoy.j2db.smart.MainPanel;
import com.servoy.j2db.smart.SwingForm;
import com.servoy.j2db.smart.SwingFormManager;
import com.servoy.j2db.smart.SwingRuntimeWindow;
import com.servoy.j2db.smart.SwingRuntimeWindowManager;
import com.servoy.j2db.smart.cmd.CmdManager;
import com.servoy.j2db.smart.dataui.FormLookupPanel;
import com.servoy.j2db.smart.plugins.SmartClientPluginAccessProvider;
import com.servoy.j2db.smart.scripting.ScriptMenuItem;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.IDeveloperURLStreamHandler;
import com.servoy.j2db.util.ILogLevel;
import com.servoy.j2db.util.Pair;
import com.servoy.j2db.util.PersistHelper;
import com.servoy.j2db.util.ServoyException;
import com.servoy.j2db.util.Settings;
import com.servoy.j2db.util.ThreadingRemoteInvocationHandler;
import com.servoy.j2db.util.Utils;
import com.servoy.j2db.util.gui.SpecialMatteBorder;
/**
* @author jcompagner
*
*/
public class DebugJ2DBClient extends J2DBClient implements IDebugJ2DBClient
{
final class DebugSwingFormMananger extends SwingFormManager implements DebugUtils.DebugUpdateFormSupport
{
private boolean solutionLoaded = false;
/**
* @param app
* @param mainContainer
*/
private DebugSwingFormMananger(ISmartClientApplication app, IMainContainer mainContainer)
{
super(app, mainContainer);
}
/**
* @see com.servoy.j2db.smart.SwingFormManager#getShowFormsAllInWindowMenu()
*/
@Override
protected boolean getShowFormsAllInWindowMenu()
{
return true;
}
public void updateForm(Form form)
{
boolean isNew = !possibleForms.containsValue(form);
boolean isDeleted = false;
if (!isNew)
{
isDeleted = !((AbstractBase)form.getParent()).getAllObjectsAsList().contains(form);
}
updateForm(form, isNew, isDeleted);
}
/**
* @param form
* @param isNew
* @param isDeleted
*/
private void updateForm(Form form, boolean isNew, boolean isDeleted)
{
if (isNew)
{
addForm(form, false);
}
else if (isDeleted)
{
windowMenuDialog.removeForm(form);
Iterator<Entry<String, Form>> iterator = possibleForms.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry<String, Form> entry = iterator.next();
if (entry.getValue().equals(form))
{
iterator.remove();
}
}
}
else
{
// just changed
if (possibleForms.get(form.getName()) == null)
{
// name change, first remove the form
updateForm(form, false, true);
// then add it back in
updateForm(form, true, false);
}
else
{
windowMenuDialog.refreshFrom(form);
}
}
}
// fill the scripts menu
@Override
protected ScriptMenuItem getScriptMenuItem(ScriptMethod sm, FunctionDefinition functionDefinition, int autoSortcut)
{
ScriptMenuItem item = new ScriptMenuItem(getApplication(), functionDefinition, sm.getName(), autoSortcut);
if (sm.getShowInMenu())
{
item.setIcon(getApplication().loadImage("showinmenuform.gif"));
}
else
{
item.setIcon(getApplication().loadImage("empty.gif"));
}
return item;
}
@Override
protected void makeSolutionSettings(Solution s)
{
IApplication app = getApplication();
if (app instanceof IDebugJ2DBClient) ((IDebugJ2DBClient)app).onSolutionOpen();
super.makeSolutionSettings(s);
if (isShutDown()) return; // for example user could have hit the stop button while solution onLoad was running or the test client solution timeout kicked in; in this case the solution is not loaded anymore!
solutionLoaded = true;
}
@Override
protected void destroySolutionSettings()
{
solutionLoaded = false;
super.destroySolutionSettings();
readOnlyCheck.clear();
}
public boolean isSolutionLoaded()
{
return solutionLoaded;
}
}
public class DebugSwingRuntimeWindowManager extends SwingRuntimeWindowManager
{
public DebugSwingRuntimeWindowManager(IApplication application)
{
super(application);
}
@Override
protected RuntimeWindow createWindowInternal(String windowName, int type, RuntimeWindow parentWindow)
{
return new SwingRuntimeWindow((ISmartClientApplication)application, windowName, type, parentWindow)
{
@Override
protected FormFrame createFormFrame(String formWindowName)
{
FormFrame ff = super.createFormFrame(formWindowName);
setUpFormWindow(ff, ff.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW), ff.getRootPane().getActionMap());
return ff;
}
@Override
protected FormDialog createFormDialog(Window owner, boolean modal, String dialogName)
{
FormDialog debugFormDialog = super.createFormDialog(owner, modal, dialogName);
setUpFormWindow(debugFormDialog, debugFormDialog.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW),
debugFormDialog.getRootPane().getActionMap());
return debugFormDialog;
}
private void setUpFormWindow(final FormWindow window, InputMap im, ActionMap am)
{
am.put("CTRL+L", new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
IMainContainer debugFormMainContainer = window.getMainContainer();
if (debugFormMainContainer != null)
{
FormController fc = debugFormMainContainer.getController();
if (fc == null) return;
Form form = fc.getForm();
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (focusOwner != null)
{
while (!(focusOwner instanceof SwingForm) && focusOwner != null)
{
focusOwner = focusOwner.getParent();
}
if (focusOwner != null)
{
form = ((SwingForm)focusOwner).getController().getForm();
}
}
DebugJ2DBClient.this.designerCallback.showFormInDesigner(form);
}
}
});
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_L, J2DBClient.menuShortcutKeyMask), "CTRL+L");
}
};
}
}
/**
* Class that is able to refresh persists sequentially and when no script is executing.
*/
// reason for creating this class:
// 1. updating debug smart client as a result of editor save => form refresh/reload that triggers JS events that do application.updateUI(...) - this messes up update sequencing when using only invokeLater(...)
// 2. updating debug smart client as a result of editor save while JS that contains application.updateUI(...) is still being executed in AWT
public class RefreshPersistsSequencer
{
SequencedRunnable lastSequencedRunnable = null;
public void runLaterInSequence(Runnable runnable)
{
boolean mustInvoke = false;
synchronized (this)
{
if (lastSequencedRunnable == null)
{
// only first time
lastSequencedRunnable = new SequencedRunnable(runnable);
mustInvoke = true;
}
else
{
Pair<SequencedRunnable, Boolean> p = lastSequencedRunnable.setNextJob(runnable);
lastSequencedRunnable = p.getLeft();
mustInvoke = p.getRight().booleanValue();
}
}
if (mustInvoke)
{
if (SwingUtilities.isEventDispatchThread())
{
lastSequencedRunnable.run();
}
else
{
SwingUtilities.invokeLater(lastSequencedRunnable);
}
}
}
private class SequencedRunnable implements Runnable
{
private static final int MAX_TIME_TO_WAIT_FOR_SCRIPTS_TO_FINISH = 15000; // ms
private Runnable r;
private SequencedRunnable next = null;
private final long creationTimestamp;
public SequencedRunnable(Runnable r)
{
this.r = r;
this.creationTimestamp = System.currentTimeMillis();
}
public void run()
{
IExecutingEnviroment se = getScriptEngine();
if (se instanceof RemoteDebugScriptEngine && ((RemoteDebugScriptEngine)se).isAWTSuspendedRunningScript() &&
(System.currentTimeMillis() - creationTimestamp < MAX_TIME_TO_WAIT_FOR_SCRIPTS_TO_FINISH))
{
// try to avoid refresh (postpone it) while inside a script application.updateUI or application.sleep, if possible with MAX_TIME_TO_WAIT_FOR_SCRIPTS_TO_FINISH ms tolerance
// added a timer instead of simply calling invokeLater, because otherwise the debug smart client UI would not repaint when using ALT+TAB in this case, although when hovering with the mouse over components, those would get repainted...
new Timer(true).schedule(new TimerTask()
{
@Override
public void run()
{
SwingUtilities.invokeLater(SequencedRunnable.this);
}
}, 1000);
}
else
{
try
{
r.run();
}
catch (Throwable e)
{
Debug.error(e);
}
synchronized (this)
{
r = null; // r is complete
if (next != null)
{
SwingUtilities.invokeLater(next); // this would probably also work by calling next.run() directly here
}
}
}
}
public synchronized Pair<SequencedRunnable, Boolean> setNextJob(Runnable runnable)
{
boolean mustInvoke = false;
next = new SequencedRunnable(runnable);
if (r == null)
{
// r is already done, we can start this right away
mustInvoke = true;
}
return new Pair<SequencedRunnable, Boolean>(next, Boolean.valueOf(mustInvoke));
}
}
}
/**
* Make sure that showLoading doesn't show the main frame at inappropriate times, like first client initialisation that
* might happen when opening the first form designer (so only allow it while in a "run" cycle).
* @author acostescu
*/
protected class DebugLoadingUIEffects extends LoadingUIEffects
{
private boolean loading = false;
private boolean clientShouldBeShowing = false;
public DebugLoadingUIEffects(J2DBClient client, MainPanel mainPanel)
{
super(client, mainPanel);
}
@Override
public void showSolutionLoading(boolean b)
{
loading = b;
if (clientShouldBeShowing)
{
super.showSolutionLoading(loading);
}
}
public void setClientShouldBeShowing(boolean clientShouldBeShowing)
{
if (clientShouldBeShowing != this.clientShouldBeShowing)
{
this.clientShouldBeShowing = clientShouldBeShowing;
if (clientShouldBeShowing) super.showSolutionLoading(loading);
else super.showSolutionLoading(false);
}
}
@Override
protected URL getWebStartURL()
{
try
{
return new URL("http://localhost:" + ApplicationServerRegistry.get().getWebServerPort());
}
catch (MalformedURLException e)
{
Debug.trace("Cannot find base URL for solution loading image...", e);
return null;
}
}
}
private Solution current;
private boolean shutDown = false;
private boolean unitTestsRunning = false;
private final IDesignerCallback designerCallback;
private final RefreshPersistsSequencer refreshPersistsSequencer;
private IBrowserLauncher browserLauncher;
@Override
public boolean isShutDown()
{
return shutDown;
}
public DebugJ2DBClient(boolean setSingletonServiceProvider, final IDesignerCallback callback)
{
super(setSingletonServiceProvider);
this.designerCallback = callback;
refreshPersistsSequencer = new RefreshPersistsSequencer();
startupApplication(new String[0]);
InputMap inputMap = mainPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
ActionMap actionMap = mainPanel.getActionMap();
actionMap.put("CTRL+L", new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
FormController fc = (FormController)getFormManager().getCurrentForm();
if (fc == null) return;
Form form = fc.getForm();
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (focusOwner != null)
{
while (!(focusOwner instanceof SwingForm) && focusOwner != null)
{
focusOwner = focusOwner.getParent();
}
if (focusOwner != null)
{
form = ((SwingForm)focusOwner).getController().getForm();
}
}
callback.showFormInDesigner(form);
}
});
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_L, menuShortcutKeyMask), "CTRL+L");
Settings.getInstance().loadUserProperties(defaultUserProperties);
}
@Override
public boolean isEventDispatchThread()
{
return super.isEventDispatchThread() || Thread.currentThread() instanceof ServoyDebugger ||
(!Utils.isAppleMacOS() && Thread.currentThread().getName().equals("JavaFX Application Thread"));
}
/**
* @see com.servoy.j2db.smart.J2DBClient#attachAppleMenu(java.util.Map)
*/
@Override
protected void attachAppleMenu(Map<String, Action> atns)
{
// dont attach anything
}
private Map<String, Action> actions;
/**
* @see com.servoy.j2db.smart.J2DBClient#getServerURL()
*/
@Override
public URL getServerURL()
{
try
{
return new URL("http://127.0.0.1:" + ApplicationServerRegistry.get().getWebServerPort());
}
catch (MalformedURLException e)
{
Debug.error(e);
}
return null;
}
private int showCounter = 0;
@Override
protected void showAd()
{
showCounter = 10;
}
public void show()
{
if (showCounter > 0)
{
if (showCounter % 10 == 0) super.showAd();
showCounter++;
}
show(null);
}
@Override
protected LoadingUIEffects createLoadingUIEffects()
{
return new DebugLoadingUIEffects(this, mainPanel);
}
@Override
protected DebugLoadingUIEffects getLoadingUIEffects()
{
return (DebugLoadingUIEffects)super.getLoadingUIEffects();
}
/**
*
*/
public void show(final Form form)
{
shutDown = false;
Runnable run = new Runnable()
{
public void run()
{
if (!getMainApplicationFrame().isVisible())
{
ComponentFactory.flushCachedItems(DebugJ2DBClient.this); // some stuff may have been cached while components are painted in form editor
getMainApplicationFrame().setVisible(true);
}
else
{
getMainApplicationFrame().setState(Frame.NORMAL);
}
getLoadingUIEffects().setClientShouldBeShowing(!shutDown);
if (unitTestsRunning)
{
getMainApplicationFrame().setState(Frame.ICONIFIED);
}
else
{
getMainApplicationFrame().toFront();
}
if (form == null)
{
if (isSolutionLoaded())
{
closeSolution(true, null);
}
else
{
selectAndOpenSolution();// fake first load
}
}
else
{
getFormManager().showFormInMainPanel(form.getName());
}
}
};
if (SwingUtilities.isEventDispatchThread())
{
run.run();
}
else
{
SwingUtilities.invokeLater(run);
}
}
/**
* @param current the current to set
*/
public void setCurrent(Solution current)
{
if (this.current != current)
{
if (this.current != null)
{
closeSolution(true, null);
}
this.current = current;
logout(null); // login (possibly dummy) from previous solution may not be valid for new one
}
}
@Override
protected LoginDialog createLoginDialog()
{
// Override login dialog, add a 'use dummy login' checkbox
return new LoginDialog(frame, this, Messages.getString("servoy.logindialog.title"), false, false /* do not show remember-me/dummy login check */)
{
@Override
protected JCheckBox createRememberMeCheckbox()
{
return new JCheckBox("Use dummy login (set in Preferences)", false);
}
@Override
public Object[] showDialog(String name)
{
if (current == null)
{
return null;
}
if (current.getMustAuthenticate())
{
return super.showDialog(name);
}
// will only get here when enhanced security is turned on and solution.mustAuthenticate = false
// Use the dummy auth to access the appserver, in real server access a login dialog would be shown
DeveloperPreferences developerPreferences = new DeveloperPreferences(Settings.getInstance());
boolean dummyAuth = developerPreferences.getUseDummyAuth();
if (!dummyAuth)
{
Object[] loginResult = super.showDialog(name);
dummyAuth = loginResult != null && loginResult.length >= 3 && Boolean.TRUE.equals(loginResult[2]);
if (!dummyAuth)
{
return loginResult;
}
developerPreferences.setUseDummyAuth(true);
}
// dummy authentication
try
{
authenticate(new Credentials(getClientID(), null, null, IApplicationServer.DUMMY_LOGIN));
}
catch (RepositoryException e)
{
Debug.error(e);
}
// return null means user hit cancel, in case of dummy login, user id has changed
handleClientUserUidChanged(null, getClientInfo().getUserUid());
return null;
}
};
}
/**
* @return the current
*/
public Solution getCurrent()
{
return current;
}
/**
* @see com.servoy.j2db.smart.J2DBClient#initRMISocketFactory()
*/
@Override
protected void initRMISocketFactory()
{
// ignore
}
@Override
protected void bindUserClient()
{
}
@Override
public synchronized int exportObject(Remote object) throws RemoteException
{
// don't export in debug client
return -1;
}
@Override
public void invokeAndWait(Runnable r)
{
if (getScriptEngine() instanceof IScriptSupport && ((IScriptSupport)getScriptEngine()).isAWTSuspendedRunningScript())
{
r.run();
}
else
{
super.invokeAndWait(r);
}
}
@Override
protected void unBindUserClient() throws Exception
{
}
/**
* @see com.servoy.j2db.smart.J2DBClient#initStreamHandlerFactory()
*/
@Override
protected void initStreamHandlerFactory()
{
// ignore
}
/**
* @see com.servoy.j2db.smart.J2DBClient#addURLStreamHandler(java.lang.String, java.net.URLStreamHandler)
*/
@Override
public void addURLStreamHandler(String protocolName, URLStreamHandler handler)
{
// TODO See Activator of application server eclipse project
if (handler instanceof IDeveloperURLStreamHandler)
{
designerCallback.addURLStreamHandler(protocolName, (IDeveloperURLStreamHandler)handler);
}
else
{
Debug.error("Tried adding protocol: " + protocolName +
" as an url stream handler in debug client, please implement the IDeveloperURLStreamHandler interface");
}
}
/**
* @see com.servoy.j2db.smart.J2DBClient#shutDown(boolean)
*/
@Override
public void shutDown(boolean force)
{
shutDown = true;
try
{
Solution solution = getSolution();
if (solution != null)
{
if (!closeSolution(force, null) && !force)
{
shutDown = false; // could not close
return;
}
}
logout(null);
if (unitTestsRunning)
{
repository = null;
applicationServerAccess = null;
}
}
catch (Exception ex)
{
Debug.error(ex);
}
getLoadingUIEffects().setClientShouldBeShowing(!shutDown);
//dispose owned windows
Window[] ownedWindows = getMainApplicationFrame().getOwnedWindows();
if (ownedWindows != null)
{
for (Window w : ownedWindows)
w.dispose();
}
getMainApplicationFrame().setVisible(false);
saveSettings();
}
public void shutDownAndDispose()
{
shutDown = true;
super.shutDown(true);
}
/**
* @see com.servoy.j2db.smart.J2DBClient#getActions()
*/
@Override
protected Map<String, Action> getActions()
{
if (actions == null)
{
actions = super.getActions();
}
return actions;
}
/**
* @see com.servoy.j2db.smart.J2DBClient#closeSolution(boolean, java.lang.Object[])
*/
@Override
public boolean closeSolution(boolean force, Object[] args)
{
boolean b = super.closeSolution(force, args);
if (b)
{
if (actions != null)
{
// don't create for close
createMenuBar(actions);
fillToolbar(actions);
String showMenuBar = settings.getProperty("servoy.smartclient.showMenuBar");
if (showMenuBar != null && showMenuBar.equals("false")) frame.getJMenuBar().setVisible(false);
else frame.getJMenuBar().setVisible(true);
String showToolBar = settings.getProperty("servoy.smartclient.showToolBar");
if (showToolBar != null && showToolBar.equals("false")) toolbarsPanel.setVisible(false);
else toolbarsPanel.setVisible(true);
}
if (getPreferedSolutionNameToLoadOnInit() == null && getMainApplicationFrame().isVisible())
{
invokeLater(new Runnable()
{
public void run()
{
invokeLater(new Runnable()
{
public void run()
{
if (solutionRoot.getMainSolutionMetaData() == null)
{
selectAndOpenSolution(); // automatically re-open solution in developer
}
}
});
}
});
}
}
return b;
}
@Override
protected boolean callCloseSolutionMethod(boolean force)
{
// do not call method if user not logged in and solution requires authentication
if (getSolution() != null && getSolution().requireAuthentication() && getUserUID() == null) return true;
return super.callCloseSolutionMethod(force);
}
/**
* @see com.servoy.j2db.smart.J2DBClient#initSettings()
*/
@Override
protected void initSettings() throws Exception
{
settings = Settings.getInstance();
}
@Override
protected ILAFManager createLAFManager()
{
return ApplicationServerRegistry.get().getLafManager();
}
@Override
protected SmartClientPluginAccessProvider createClientPluginAccessProvider()
{
return new SmartClientPluginAccessProvider(this)
{
private final boolean useSerializingDataserverProxy = new DeveloperPreferences(Settings.getInstance()).useSerializingDataserverProxy();
@Override
public Remote getRemoteService(String name) throws Exception
{
// (de)serialize arguments and result of 'remote' services in developer to mimic rmi in smart client
Remote remoteService = super.getRemoteService(name);
if (useSerializingDataserverProxy)
{
return SerializingRemoteInvocationHandler.createSerializingSerializingInvocationHandler(DebugJ2DBClient.this, remoteService);
}
return remoteService;
}
};
}
@Override
protected IDataServer createDataServer()
{
IDataServer dataServer = super.createDataServer();
if (dataServer != null)
{
if (new DeveloperPreferences(Settings.getInstance()).useSerializingDataserverProxy())
{
dataServer = SerializingRemoteInvocationHandler.createSerializingSerializingInvocationHandler(this,
ThreadingRemoteInvocationHandler.createThreadingRemoteInvocationHandler(dataServer, new Class< ? >[] { IDataServer.class }),
new Class[] { IDataServer.class });
}
dataServer = new ProfileDataServer(dataServer);
}
return dataServer;
}
@Override
protected IBeanManager createBeanManager()
{
return ApplicationServerRegistry.get().getBeanManager();
}
@Override
protected IFormManagerInternal createFormManager()
{
return new DebugSwingFormMananger(this, mainPanel);
}
@Override
protected RuntimeWindowManager createJSWindowManager()
{
return new DebugSwingRuntimeWindowManager(this);
}
// overridden ssl-rmi seems not to work locally
@Override
protected IApplicationServer connectApplicationServer() throws Exception
{
return ApplicationServerRegistry.getService(IDebugApplicationServer.class);
}
/**
* @see com.servoy.j2db.smart.server.headlessclient.SessionClient#createScriptEngine()
*/
@Override
protected IExecutingEnviroment createScriptEngine()
{
RemoteDebugScriptEngine engine = new RemoteDebugScriptEngine(this);
if (designerCallback != null)
{
designerCallback.addScriptObjects(this, engine.getSolutionScope());
}
return engine;
}
@Override
protected IActiveSolutionHandler createActiveSolutionHandler()
{
return new RemoteActiveSolutionHandler(getApplicationServer(), this)
{
@Override
public void saveActiveSolution(Solution solution)
{
// no solution saving in debugger
}
};
}
@Override
public void output(Object message, int level)
{
super.output(message, level);
if (level == ILogLevel.WARNING || level == ILogLevel.ERROR)
{
DebugUtils.errorToDebugger(getScriptEngine(), message.toString(), null);
}
else
{
DebugUtils.stdoutToDebugger(getScriptEngine(), message);
}
}
/**
* @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);
}
@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 reportInfo(Component parentComponent, String message, String title)
{
DebugUtils.infoToDebugger(getScriptEngine(), message);
super.reportInfo(parentComponent, message, title);
}
/**
* @see com.servoy.j2db.smart.J2DBClient#reportError(java.awt.Component, java.lang.String, java.lang.Object)
*/
@Override
public void reportError(final Component parentComponent, String msg, Object detail)
{
String message = msg;
DebugUtils.errorToDebugger(getScriptEngine(), message, detail);
Debug.error(detail);
mainPanel.getToolkit().beep();
if (detail instanceof ServoyException)
{
message = ((ServoyException)detail).getMessage();
}
else if (detail instanceof RhinoException)
{
RhinoException re = (RhinoException)detail;
if (re.getCause() != null) message = re.getCause().getLocalizedMessage();
}
if (message == null) message = "";
if (message.length() > 100)
{
message = message.substring(0, 100) + "...";
}
final String m = message;
// invoke later in the debug client else it can block the debugger.
invokeLater(new Runnable()
{
public void run()
{
if (parentComponent.isVisible())
{
JOptionPane.showMessageDialog(parentComponent, m, Messages.getString("servoy.general.error"), JOptionPane.ERROR_MESSAGE);
}
}
});
}
public void refreshForI18NChange(boolean recreateForms)
{
if (shutDown) return;
refreshI18NMessages();
if (recreateForms)
{
Runnable run = new Runnable()
{
@Override
public void run()
{
for (IFormController fc : getFormManager().getCachedFormControllers())
{
fc.recreateUI();
}
}
};
if (SwingUtilities.isEventDispatchThread())
{
run.run();
}
else
{
SwingUtilities.invokeLater(run);
}
}
}
/**
* @param changes
*/
public void refreshPersists(final Collection<IPersist> changes)
{
if (shutDown) return;
refreshPersistsSequencer.runLaterInSequence(new Runnable()
{
public void run()
{
refreshPersistsNow(changes);
}
});
}
private void refreshPersistsNow(Collection<IPersist> changes)
{
if (shutDown) return;
Set<IFormController>[] scopesAndFormsToReload = DebugUtils.getScopesAndFormsToReload(this, changes);
for (IFormController controller : scopesAndFormsToReload[1])
{
destroyForm(controller);
}
for (IFormController controller : scopesAndFormsToReload[0])
{
if (controller.getForm() instanceof FlattenedForm)
{
FlattenedForm ff = (FlattenedForm)controller.getForm();
ff.reload();
}
controller.getFormScope().reload();
}
}
/**
* @param formController
* @return
*/
private void destroyForm(IFormController formController)
{
refreshI18NMessages();
if (formController.isFormVisible())
{
IFoundSetInternal foundSet = formController.getFormModel();
if (foundSet instanceof FoundSet)
{
((FoundSet)foundSet).refresh();
}
String name = null;
if (formController.getForm() != null) name = formController.getForm().getName();
if (name == null) name = formController.getName();
if (getFormManager().getCurrentForm() == formController)
{
formController.destroy();
getFormManager().showFormInCurrentContainer(name);
}
else
{
SwingForm swingForm = (SwingForm)formController.getFormUI();
Container container = swingForm.getParent();
boolean isNavigator = false;
boolean isWindow = false;
boolean isLookupPanel = false;
if (container instanceof MainPanel)
{
isNavigator = ((MainPanel)container).getNavigator() == formController;
}
else if (container instanceof FormLookupPanel)
{
isLookupPanel = true;
}
else
{
while (container != null && !(container instanceof FormWindow))
{
container = container.getParent();
}
if (container instanceof FormWindow)
{
isWindow = true;
}
}
formController.destroy();
if (isLookupPanel)
{
FormLookupPanel flp = (FormLookupPanel)container;
FormController newFormController = flp.getFormPanel();
if (newFormController != null)
{
// deleted in developer ?
newFormController.loadData(foundSet, null);
List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>();
newFormController.notifyVisible(true, invokeLaterRunnables);
Utils.invokeLater(this, invokeLaterRunnables);
}
}
else if (isNavigator)
{
// TODO isNavigator check will always be false for NGClient?
FormController navigator = ((FormManager)getFormManager()).getFormController(name, container);
if (navigator != null)
{
navigator.loadData(foundSet, null);
List<Runnable> invokeLaterRunnables = new ArrayList<Runnable>();
navigator.notifyVisible(true, invokeLaterRunnables);
Utils.invokeLater(this, invokeLaterRunnables);
}
mainPanel.setNavigator(navigator);
}
else if (isWindow)
{
// TODO isWindow check will always be false for NGClient?
FormWindow w = (FormWindow)container;
((FormManager)getFormManager()).showFormInMainPanel(name, w.getMainContainer(), w.getTitle(), false, w.getName());
}
}
}
else
{
formController.destroy();
}
return;
}
@Override
protected CmdManager createCmdManager()
{
CmdManager cmdM = super.createCmdManager();
// the AutoOpenSolution must be set to false in order to avoid at least one NullPointerException;
// because this DebugJ2DBClient constructor runs in the AWT thread and it will also call invokeLater(...) on the AWT thread
// this can cause problems in Eclipse. Other code in the Eclipse threads might run in parallel. For example,
// a Form Designer editor is being opened and does things with the FlattenedSolution while this AWT thread
// calls close() on the flattened solution when trying to show the open solution dialog (=> the NullPointerException I originally mentioned).
cmdM.setAutoOpenSolutionSelectDialog(false);
return cmdM;
}
public boolean isDoneLoading()
{
return ((DebugSwingFormMananger)formManager).isSolutionLoaded();
}
public void setUnitTestMode(boolean b)
{
unitTestsRunning = b;
}
@Override
public void logout(Object[] solutionToOpenArgs)
{
if (unitTestsRunning)
{
// in tests, it is sometimes useful to be able to logout without closing the test solution - so that you can login with a different user
if (getClientInfo().getClientId() != null)
{
try
{
IApplicationServerAccess asa = getApplicationServerAccess();
if (asa != null)
{
asa.logout(getClientInfo().getClientId());
}
getClientInfo().clearUserInfo();
// else not logged in
}
catch (Exception e)
{
Debug.error("Error during logout", e);
}
}
}
else
{
super.logout(solutionToOpenArgs);
}
}
@Override
protected SolutionMetaData selectSolutionToLoad() throws RepositoryException
{
return (current == null) ? null : current.getSolutionMetaData();
}
@Override
protected boolean getAppleScreenMenuBar()
{
return false;
}
@Override
protected void installShutdownHook()
{
//don't install the shutdownhook;
}
@Override
protected void startupApplication(String[] args)
{
super.startupApplication(args);
try
{
Introspector.getBeanInfo(SpecialMatteBorder.class).getBeanDescriptor().setValue("persistenceDelegate",
new DefaultPersistenceDelegate(new String[] { "top", "left", "bottom", "right", "topColor", "leftColor", "bottomColor", "rightColor" }));
}
catch (IntrospectionException e)
{
Debug.error(e);
}
}
public void setBrowserLauncher(IBrowserLauncher browserLauncher)
{
this.browserLauncher = browserLauncher;
}
public IBrowserLauncher getBrowserLauncher()
{
return browserLauncher;
}
@Override
public boolean showURL(String url, String target, String target_options, int timeout, boolean closeDialogs)
{
return browserLauncher != null ? browserLauncher.showURL(url) : super.showURL(url, target, target_options, timeout, closeDialogs);
}
@Override
protected void exitHard(int status)
{
// Do not exit developer
if (!isShutDown())
{
shutDown(true);
}
}
Map<String, String> defaultUserProperties = new HashMap<String, String>();
@Override
public void setUserProperty(String name, String value)
{
defaultUserProperties.remove(name);
((Settings)getSettings()).setUserProperty(Settings.DEVELOPER_USER, name, value);
}
@Override
public String getUserProperty(String name)
{
if (defaultUserProperties.containsKey(name))
{
return defaultUserProperties.get(name);
}
return ((Settings)getSettings()).getUserProperty(Settings.DEVELOPER_USER, name);
}
@Override
public String[] getUserPropertyNames()
{
List<String> userPropertyNames = new ArrayList<String>(defaultUserProperties.keySet());
Iterator<Object> it = getSettings().keySet().iterator();
while (it.hasNext())
{
String key = (String)it.next();
if (key.startsWith(Settings.DEVELOPER_USER))
{
String name = key.substring(Settings.DEVELOPER_USER.length());
if (!userPropertyNames.contains(name))
{
userPropertyNames.add(name);
}
}
}
return userPropertyNames.toArray(new String[0]);
}
private HashMap<Object, Object> changedProperties;
private boolean wasLoginSolution;
@Override
public boolean putClientProperty(Object name, Object value)
{
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, value);
}
public void onSolutionOpen()
{
if (changedProperties == null)
{
changedProperties = new HashMap<Object, Object>();
}
else
{
if (!wasLoginSolution)
{
if (changedProperties.containsKey(LookAndFeelInfo.class.getName()))
{
String selectedlnfSetting = getSettings().getProperty("selectedlnf");
if (selectedlnfSetting != null) changedProperties.put(LookAndFeelInfo.class.getName(), selectedlnfSetting);
}
if (changedProperties.containsKey(Font.class.getName()))
{
String font = getSettings().getProperty("font");
if (font != null) changedProperties.put(Font.class.getName(), PersistHelper.createFont(font));
}
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;
}
}
}
}