/*
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;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.URL;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.UnmarshalException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import java.util.concurrent.ScheduledExecutorService;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Undefined;
import com.servoy.base.persistence.constants.IValueListConstants;
import com.servoy.j2db.component.ComponentFactory;
import com.servoy.j2db.dataprocessing.ClientInfo;
import com.servoy.j2db.dataprocessing.CustomValueList;
import com.servoy.j2db.dataprocessing.DataServerProxy;
import com.servoy.j2db.dataprocessing.FoundSetManager;
import com.servoy.j2db.dataprocessing.IClientHost;
import com.servoy.j2db.dataprocessing.IDataServer;
import com.servoy.j2db.dataprocessing.IFoundSetManagerInternal;
import com.servoy.j2db.dataprocessing.ISaveConstants;
import com.servoy.j2db.dataprocessing.IUserClient;
import com.servoy.j2db.dataprocessing.IValueList;
import com.servoy.j2db.persistence.ClientMethodTemplatesLoader;
import com.servoy.j2db.persistence.IActiveSolutionHandler;
import com.servoy.j2db.persistence.IColumnTypes;
import com.servoy.j2db.persistence.IRepository;
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.persistence.ValueList;
import com.servoy.j2db.plugins.IPluginAccess;
import com.servoy.j2db.plugins.IPluginManagerInternal;
import com.servoy.j2db.scripting.IExecutingEnviroment;
import com.servoy.j2db.scripting.StartupArguments;
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.IClientManager;
import com.servoy.j2db.server.shared.IUserManager;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.ServoyException;
import com.servoy.j2db.util.Settings;
import com.servoy.j2db.util.Utils;
import com.servoy.j2db.util.serialize.JSONConverter;
/**
* Bare bone state and abstract base class for all client process instance
*
* @author jblok
*/
public abstract class ClientState extends ClientVersion implements IServiceProvider, Serializable
{
public static String READY = Messages.getString("servoy.general.status.ready"); //$NON-NLS-1$
/**
* Fields
*/
//provides basic server interaction
protected transient IApplicationServer applicationServer;
//provides extended server access after authentication
protected transient IApplicationServerAccess applicationServerAccess;
//local reference to repository
protected transient IRepository repository = null;
//local reference to dataserver
private transient IDataServer dataServer;
//local reference to client host
private transient IClientHost clientHost;
//the script engine
private volatile IExecutingEnviroment scriptEngine;
//holding the application setting
protected Properties settings;
//preferred solution (args)
protected String preferredSolutionNameToLoadOnInit = null;
protected String preferredSolutionMethodNameToCall = null;
protected Object[] preferredSolutionMethodArguments = null;
//the main solution, also called root
protected final FlattenedSolution solutionRoot = new FlattenedSolution();
/**
* Managers
*/
//form manager handling the forms
protected volatile IBasicFormManager formManager;
//mode manager handling the application mode
protected transient volatile IModeManager modeManager;
//foundset manager handling the foundsets
protected transient volatile IFoundSetManagerInternal foundSetManager;
//plugin manager handling the (scriptable plugins)
protected transient volatile IPluginManagerInternal pluginManager;
//user manager, giving access to (other)user info
private transient volatile IUserManager userManager;
// does this client use the login solution when configured?
private volatile boolean useLoginSolution = true;
// boolean set to true, right after the solution is closed (right after when the solution onclose method is called)
private volatile boolean solutionClosed;
protected transient volatile IUserClient userClient;
private volatile ClientInfo clientInfo;
private transient volatile boolean isShutdown;
protected ClientState()
{
clientInfo = new ClientInfo();
// firing some form events needs to know the position of JSEvent argument
ClientMethodTemplatesLoader.loadClientMethodTemplatesIfNeeded();
}
protected void logStartUp()
{
Debug.log("Starting Servoy from " + System.getProperty("user.dir")); //$NON-NLS-1$//$NON-NLS-2$
Debug.log("Servoy " + getVersion() + " build-" + getReleaseNumber() + " on " + System.getProperty("os.name") + " using Java " + System.getProperty("java.version")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
// Debug.log("file.encoding " + System.getProperty("file.encoding"));
}
private void appendArgumentsScopeToPreferedSolutionMethodArguments(StartupArguments argumentsScope)
{
if (preferredSolutionMethodArguments != null && preferredSolutionMethodArguments.length == 1)
{
Object[] new_preferedSolutionMethodArguments = new Object[preferredSolutionMethodArguments.length + 1];
System.arraycopy(preferredSolutionMethodArguments, 0, new_preferedSolutionMethodArguments, 0, preferredSolutionMethodArguments.length);
new_preferedSolutionMethodArguments[preferredSolutionMethodArguments.length] = argumentsScope.toJSMap();
preferredSolutionMethodArguments = new_preferedSolutionMethodArguments;
}
}
public void handleArguments(String args[], StartupArguments argumentsScope)
{
handleArguments(args);
appendArgumentsScopeToPreferedSolutionMethodArguments(argumentsScope);
}
private StartupArguments argumentsScope;
public void handleArguments(String[] args)
{
String[] filteredArgs = null;
// filter out all system.properties
if (args != null)
{
ArrayList<String> filteredArgsList = new ArrayList<String>();
for (String arg : args)
{
if (arg != null && arg.startsWith("system.property.")) continue; //$NON-NLS-1$
filteredArgsList.add(arg);
}
filteredArgs = new String[filteredArgsList.size()];
filteredArgs = filteredArgsList.toArray(filteredArgs);
}
clientInfo.setSpecialClientIndentifier(null);
if (filteredArgs == null || filteredArgs.length == 0)
{
//clear, do not clear method and arguments (clear method when it is called, we want to access the arguments during the app livespan)
preferredSolutionNameToLoadOnInit = null;
}
else
{
argumentsScope = new StartupArguments(filteredArgs);
if (argumentsScope.getSolutionName() == null && argumentsScope.getMethodName() == null && argumentsScope.getFirstArgument() == null &&
argumentsScope.getClientIdentifier() == null)
{
preferredSolutionNameToLoadOnInit = filteredArgs[0];
if (filteredArgs.length >= 2)
{
if (filteredArgs[1] != null && filteredArgs[1].startsWith("CI:")) //$NON-NLS-1$
{
clientInfo.setSpecialClientIndentifier(filteredArgs[1].substring(3));
}
else
{
preferredSolutionMethodNameToCall = filteredArgs[1];
}
preferredSolutionMethodArguments = null;
if (filteredArgs.length >= 3)
{
if (filteredArgs[2] != null && filteredArgs[2].startsWith("CI:")) //$NON-NLS-1$
{
clientInfo.setSpecialClientIndentifier(filteredArgs[2].substring(3));
}
else
{
preferredSolutionMethodArguments = new Object[] { filteredArgs[2] };
}
if (filteredArgs.length >= 4 && filteredArgs[3] != null && filteredArgs[3].startsWith("CI:")) //$NON-NLS-1$
{
clientInfo.setSpecialClientIndentifier(filteredArgs[3].substring(3));
}
}
}
}
else
{
preferredSolutionNameToLoadOnInit = argumentsScope.getSolutionName();
preferredSolutionMethodNameToCall = argumentsScope.getMethodName();
preferredSolutionMethodArguments = argumentsScope.getFirstArgument() != null ? new Object[] { argumentsScope.getFirstArgument() } : null;
if (argumentsScope.getClientIdentifier() != null) clientInfo.setSpecialClientIndentifier(argumentsScope.getClientIdentifier());
appendArgumentsScopeToPreferedSolutionMethodArguments(argumentsScope);
}
}
}
public Object[] getPreferedSolutionMethodArguments()
{
return preferredSolutionMethodArguments;
}
public String getPreferedSolutionMethodNameToCall()
{
return preferredSolutionMethodNameToCall;
}
public String getPreferedSolutionNameToLoadOnInit()
{
return preferredSolutionNameToLoadOnInit;
}
public void resetPreferedSolutionMethodNameToCall()
{
preferredSolutionMethodNameToCall = null;
}
protected void applicationSetup()
{
refreshI18NMessages();
TimeZone defaultTimeZone = TimeZone.getDefault();
if (defaultTimeZone != null) //can this happen?
{
getClientInfo().setTimeZone(defaultTimeZone);
}
// create modemanager
modeManager = createModeManager();
// create formmanager
formManager = createFormManager();
// Runtime.getRuntime().addShutdownHook(new Thread()
// {
// public void run()
// {
// shutDown(true,false);//last arg is false because we are exiting
// }
// });
}
protected boolean applicationInit()
{
registerListeners();
createUserClient();
//Loading plugins
//setStatusText(Messages.getString("servoy.client.status.load.plugins")); //$NON-NLS-1$
return true;
}
protected boolean applicationServerInit() throws Exception
{
boolean b = startApplicationServerConnection();
bindUserClient();
registerClient(userClient);
createPluginManager();
return b;
}
//likely called on non awt thread
protected boolean serverInit()
{
return true;
}
public IUserManager getUserManager()
{
if (userManager == null)
{
synchronized (this)
{
if (userManager == null)
{
userManager = createUserManager();
}
}
}
return userManager;
}
@SuppressWarnings("nls")
protected IUserManager createUserManager()
{
IApplicationServerAccess asa = getApplicationServerAccess();
if (asa == null)
{
return null;
}
try
{
return asa.getUserManager(getClientID());
}
catch (RemoteException e)
{
Debug.error("Cannot get user manager", e);
}
return null;
}
/**
* @return success
*/
protected abstract boolean startApplicationServerConnection();
protected abstract void bindUserClient();
public void unRegisterClient(String client_id) throws RemoteException
{
IClientHost ch = getClientHost();
if (ch != null)
{
ch.unregister(client_id);
}
}
/**
* Handle client login/logout.
*
* @param userUidBefore
* @param userUidAfter
*/
public void handleClientUserUidChanged(String userUidBefore, String userUidAfter)
{
if (userUidBefore == null && userUidAfter == null) return;
if (isShutDown() || isClosing)
{
return;
}
if (solutionRoot.isMainSolutionLoaded() && (userUidAfter != null || !solutionRoot.getSolution().requireAuthentication()))
{
// no need to load main solution, user already logged in or does not have to log-in
return;
}
if (userUidAfter == null)
{
// user logged out, close solution
closeSolution(true, null);
}
else if (!userUidAfter.equals(userUidBefore))
{
// user logged in or switched user
solutionRoot.clearLoginSolution(getActiveSolutionHandler());
}
// open the solution previously selected or show solution selection
selectAndOpenSolution();
}
public void selectAndOpenSolution()
{
if (isShutDown() || isClosing)
{
return;
}
SolutionMetaData solutionMetaData = null;
try
{
solutionMetaData = solutionRoot.getMainSolutionMetaData();
if (solutionMetaData == null)
{
solutionMetaData = selectSolutionToLoad();
}
// else user is logged in, main solution should be loaded now
if (solutionMetaData != null)
{
loadSolution(solutionMetaData);
}
}
catch (RepositoryException e)
{
Debug.error("Could not load solution " + (solutionMetaData == null ? "<none>" : solutionMetaData.getName()), e); //$NON-NLS-1$ //$NON-NLS-2$
reportError(
Messages.getString("servoy.client.error.loadingsolution", new Object[] { solutionMetaData == null ? "<none>" : solutionMetaData.getName() }), e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
protected SolutionMetaData selectSolutionToLoad() throws RepositoryException
{
// get a list from the server to choose from
int solutionTypeFilter = getSolutionTypeFilter();
if (getPreferedSolutionNameToLoadOnInit() != null)
{
try
{
SolutionMetaData startSolution = applicationServer.getSolutionDefinition(getPreferedSolutionNameToLoadOnInit(), solutionTypeFilter);
if (startSolution != null) return startSolution;
}
catch (RemoteException e)
{
throw new RepositoryException(e);
}
}
SolutionMetaData[] solutions;
try
{
solutions = applicationServer.getSolutionDefinitions(solutionTypeFilter);
}
catch (RemoteException e)
{
throw new RepositoryException(e);
}
if (solutions == null || solutions.length == 0)
{
if (!Utils.getAsBoolean(Settings.getInstance().getProperty("servoy.allowSolutionBrowsing", "true")))
{
if (argumentsScope != null)
{
try
{
SolutionMetaData smd = applicationServer.getSolutionDefinition(argumentsScope.getSolutionName(), solutionTypeFilter);
if (smd != null) return smd;
}
catch (RemoteException e)
{
throw new RepositoryException(e);
}
}
}
throw new RuntimeException(Messages.getString("servoy.client.error.opensolution")); //$NON-NLS-1$
}
if (solutions.length == 1)
{
return solutions[0];
}
// show a dialog
return showSolutionSelection(solutions);
}
abstract protected void loadSolution(SolutionMetaData solutionMeta) throws RepositoryException;
abstract protected SolutionMetaData showSolutionSelection(SolutionMetaData[] solutions);
protected int getSolutionTypeFilter()
{
return SolutionMetaData.SOLUTION;
}
public Object authenticate(String authenticator_solution, String method, Object[] credentials) throws RepositoryException
{
String jscredentials;
JSONConverter jsonConverter;
try
{
jsonConverter = new JSONConverter(foundSetManager);
jscredentials = jsonConverter.convertToJSON(credentials);
}
catch (Exception e)
{
Debug.error("Could not convert credentials object to json", e); //$NON-NLS-1$
return null;
}
String jsReturn = authenticate(new Credentials(clientInfo.getClientId(), authenticator_solution, method, jscredentials));
try
{
return jsonConverter.convertFromJSON(jsReturn);
}
catch (Exception e)
{
Debug.error("Could not convert authentication json result to object", e); //$NON-NLS-1$
return null;
}
}
public String authenticate(Credentials credentials) throws RepositoryException
{
try
{
ClientLogin login = applicationServer.login(credentials);
if (login == null)
{
clientInfo.setUserUid(null);
return null;
}
clientInfo.setClientId(login.getClientId());
clientInfo.setUserName(login.getUserName());
clientInfo.setUserUid(login.getUserUid());
clientInfo.setUserGroups(login.getUserGroups());
if (login.getUserUid() == null)
{
clientInfo.setLastAuthentication(null, null, null);
}
else
{
// keep last successful authentication result for reconnect
clientInfo.setLastAuthentication(credentials.getAuthenticatorType(), credentials.getMethod(), credentials.getJscredentials());
loggedIn();
}
return login.getJsReturn();
}
catch (RemoteException e)
{
throw new RepositoryException(e);
}
}
protected void loggedIn()
{
// do nothing here
}
public void logout(@SuppressWarnings("unused")
Object[] solution_to_open_args)
{
String userUid = null;
try
{
if (clientInfo.getClientId() != null)
{
userUid = clientInfo.getUserUid();
try
{
IApplicationServerAccess asa = getApplicationServerAccess();
if (asa != null)
{
asa.logout(clientInfo.getClientId());
}
// else not logged in
}
catch (Exception e)
{
Debug.error("Error during logout", e); //$NON-NLS-1$
}
}
}
finally
{
clientInfo.clearUserInfo();
dataServer = null;
repository = null;
userManager = null;
applicationServerAccess = null; // will be recreated at new login
}
handleClientUserUidChanged(userUid, null);
}
protected boolean registerClient(IUserClient uc) throws Exception
{
String prevClientId = clientInfo.getClientId();
long t1 = System.currentTimeMillis();
int counter = 0;
IClientHost mClientHost = null;
Object[] retval = null;
while (counter++ < 10)
{
try
{
mClientHost = getClientHost();
if (mClientHost != null)
{
retval = mClientHost.register(uc, getClientInfo());
}
break;
}
catch (Exception e)
{
if (counter == 10)
{
if (e instanceof RemoteException)
{
throw (RemoteException)e;
}
if (e instanceof RuntimeException)
{
throw (RuntimeException)e;
}
throw new RuntimeException(e.getMessage());
}
try
{
Thread.sleep(100 * counter);
}
catch (InterruptedException e1)
{
}
}
}
boolean registered = false;
if (retval != null)
{
registered = ((Integer)retval[1]).intValue() == IClientManager.REGISTER_OK;
clientInfo.setClientId((String)retval[0]);
if (Debug.tracing())
{
Debug.trace("Client (re)registered with id " + clientInfo.getClientId() + ", previous: " + prevClientId); //$NON-NLS-1$ //$NON-NLS-2$
}
}
if (clientInfo.getClientId() == null)
{
if (mClientHost == null)
{
throw new ApplicationException(ServoyException.InternalCodes.INVALID_RMI_SERVER_CONNECTION);
}
else
{
if (retval != null && ((Integer)retval[1]).intValue() == IClientManager.REGISTER_FAILED_MAINTENANCE_MODE) throw new ApplicationException(
ServoyException.MAINTENANCE_MODE);
else throw new ApplicationException(ServoyException.NO_LICENSE);
}
}
else
{
clientInfo.setLoginTimestamp(System.currentTimeMillis());
}
long t2 = System.currentTimeMillis();
Debug.trace("Leave registerClient registered:" + registered + " in " + (t2 - t1) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
return registered;
}
protected abstract void unBindUserClient() throws Exception;
public Properties getSettings()
{
return settings;
}
private final Map<Object, Object> runtimeProperties = new HashMap<Object, Object>();
public Map<Object, Object> getRuntimeProperties()
{
return runtimeProperties;
}
public Solution getSolution()
{
return solutionRoot.getSolution();
}
/*
* (non-Javadoc)
*
* @see com.servoy.j2db.IServiceProvider#isSolutionLoaded()
*/
public boolean isSolutionLoaded()
{
return solutionRoot.getSolution() != null && !solutionClosed;
}
public String getSolutionName()
{
// return name of main solution, also when user is in login solution
return solutionRoot.getMainSolutionMetaData().getName();
}
/**
* @see com.servoy.j2db.IServiceProvider#getFlattenedSolution()
*/
public FlattenedSolution getFlattenedSolution()
{
return solutionRoot;
}
public IApplicationServer getApplicationServer()
{
return applicationServer;
}
@SuppressWarnings("nls")
public IRepository getRepository()
{
if (repository == null)
{
try
{
repository = createRepository();
if (repository != null) J2DBGlobals.firePropertyChange(this, "repository", null, repository);
}
catch (Exception ex)
{
reportError("Cannot find repository, it may not be running on server", ex);
}
}
return repository;
}
protected IRepository createRepository() throws RemoteException
{
IApplicationServerAccess asa = getApplicationServerAccess();
if (asa == null)
{
return null;
}
return asa.getRepository();
}
public final IDataServer getDataServer()
{
if (dataServer == null)
{
try
{
if (dataServer == null)
{
dataServer = createDataServer();
}
}
catch (Exception ex)
{
reportError("Cannot find dataservice, it may not be running on server", ex); //$NON-NLS-1$
}
}
return dataServer;
}
public IClientHost getClientHost()
{
if (clientHost == null)
{
try
{
if (clientHost == null)
{
clientHost = createClientHost();
}
}
catch (Exception ex)
{
reportError("Cannot find client host, it may not be running on server", ex); //$NON-NLS-1$
}
}
return clientHost;
}
public IApplicationServerAccess getApplicationServerAccess()
{
if (applicationServerAccess == null && getClientInfo().getClientId() != null)
{
applicationServerAccess = createApplicationServerAccess();
}
return applicationServerAccess;
}
public boolean haveRepositoryAccess()
{
return getRepository() != null;
}
protected IApplicationServerAccess createApplicationServerAccess()
{
if (getClientInfo().getClientId() != null)
{
try
{
return applicationServer.getApplicationServerAccess(getClientInfo().getClientId());
}
catch (RemoteException e)
{
Debug.error(e);
}
}
return null;
}
protected IDataServer createDataServer()
{
try
{
IApplicationServerAccess asa = getApplicationServerAccess();
if (asa != null)
{
return asa.getDataServer();
}
}
catch (RemoteException e)
{
Debug.error(e);
reportError(getI18NMessage("servoy.client.error.finding.repository"), e); //$NON-NLS-1$
}
return null;
}
protected IClientHost createClientHost()
{
try
{
IApplicationServer as = getApplicationServer();
if (as != null)
{
return as.getClientHost();
}
}
catch (RemoteException e)
{
Debug.error(e);
}
return null;
}
public Remote getServerService(String name)
{
try
{
return applicationServer.getRemoteService(getClientID(), name);
}
catch (RemoteException e)
{
Debug.error("Error getting the service " + name, e); //$NON-NLS-1$
reportJSError("Error getting the service " + name, e.getCause()); //$NON-NLS-1$
}
return null;
}
public IExecutingEnviroment getScriptEngine()
{
if (scriptEngine == null && solutionRoot.getSolution() != null)
{
synchronized (this)
{
if (scriptEngine == null && solutionRoot.getSolution() != null)
{
scriptEngine = createScriptEngine();
scriptEngine.getScopesScope().createGlobalsScope();
}
}
}
return scriptEngine;
}
protected abstract IExecutingEnviroment createScriptEngine();
public IModeManager getModeManager()
{
return modeManager;
}
protected abstract IModeManager createModeManager();
public IBasicFormManager getFormManager()
{
return formManager;
}
protected abstract IBasicFormManager createFormManager();
public IFoundSetManagerInternal getFoundSetManager()
{
if (foundSetManager == null && !isShutDown())
{
synchronized (this)
{
if (foundSetManager == null)
{
createFoundSetManager();
}
}
}
return foundSetManager;
}
protected abstract void createFoundSetManager();
public String getClientID()
{
if (clientInfo == null)
{
return null;
}
return clientInfo.getClientId();
}
public String getUserUID()
{
if (clientInfo == null)
{
return null;
}
return clientInfo.getUserUid();
}
public String getUserName()
{
if (clientInfo == null)
{
return null;
}
return clientInfo.getUserName();
}
public void reportError(String msg, Object detail)
{
logError(msg, detail);
}
public void reportJSError(String msg, Object detail)
{
logError(msg, detail);
}
protected void logError(String msg, Object detail)
{
if (detail instanceof Throwable)
{
Debug.error(msg, (Throwable)detail);
}
else
{
Debug.error(msg);
if (detail != null) Debug.error(detail);
}
}
public void reportWarning(String s)
{
Debug.log(s);
}
public void reportJSWarning(String s)
{
Debug.log(s);
}
public void reportJSInfo(String s)
{
Debug.debug(s);
}
public abstract ScheduledExecutorService getScheduledExecutor();
public abstract boolean isRunningRemote();
public abstract URL getServerURL();
protected void createUserClient()
{
userClient = new ClientStub(this);
}
public ClientInfo getClientInfo()
{
return clientInfo;
}
public void addClientInfo(String info)
{
clientInfo.addInfo(info);
// try to push the new client info
try
{
getClientHost().pushClientInfo(clientInfo.getClientId(), clientInfo);
}
catch (Exception e)
{
Debug.error(e);
}
}
public boolean removeClientInfo(String info)
{
boolean removed = clientInfo.removeInfo(info);
// try to push the new client info
try
{
getClientHost().pushClientInfo(clientInfo.getClientId(), clientInfo);
}
catch (Exception e)
{
Debug.error(e);
}
return removed;
}
public void removeAllClientInfo()
{
clientInfo.removeAllInfo();
// try to push the change
try
{
getClientHost().pushClientInfo(clientInfo.getClientId(), clientInfo);
}
catch (Exception e)
{
Debug.error(e);
}
}
public boolean isShutDown()
{
return isShutdown;
}
public void shutDown(boolean force)
{
Debug.trace("shutDown"); //$NON-NLS-1$
try
{
if (solutionRoot.getSolution() != null)
{
// shutdown should not try to reopen preferred solution
if (!closeSolution(force, new String[] { }) && !force) return;
// solutionRoot.setSolution(null);
}
}
catch (Exception ex)
{
Debug.error(ex);
}
isShutdown = true;
unRegisterListeners();
try
{
if (pluginManager != null)
{
pluginManager.flushCachedItems();
pluginManager = null;
}
}
catch (Exception e1)
{
Debug.error("Error flushing plugins"); //$NON-NLS-1$
}
try
{
if (formManager != null)
{
formManager.flushCachedItems();
formManager = null;
}
}
catch (Exception e1)
{
Debug.error(e1);
}
try
{
if (foundSetManager != null)
{
foundSetManager.flushCachedItems();
foundSetManager = null;
}
}
catch (Exception e1)
{
Debug.error(e1);
}
saveSettings();
//de register myself
try
{
if (clientInfo != null)
{
unRegisterClient(clientInfo.getClientId());
unBindUserClient();
clientInfo = null;
}
}
catch (Exception e)
{
Debug.error(e);// incase server is dead
}
}
protected abstract void saveSettings();
protected transient boolean isClosing = false;
protected String[] startupArguments;
public boolean closeSolution(boolean force, Object[] args)
{
if (solutionRoot.getSolution() == null || isClosing) return true;
try
{
isClosing = true;
String[] s_args = null;
// we dont want to open anything again if this was a force close
if (!force && args != null)
{
s_args = new String[args.length];
for (int i = 0; i < args.length; i++)
{
s_args[i] = (args[i] != null && args[i] != Scriptable.NOT_FOUND && args[i] != Undefined.instance ? args[i].toString() : null);
}
}
else if (!force && args == null)
{
if (getPreferedSolutionNameToLoadOnInit() != null && isInDeveloper()) s_args = new String[] { getPreferedSolutionNameToLoadOnInit() };
if (!Utils.getAsBoolean(Settings.getInstance().getProperty("servoy.allowSolutionBrowsing", "true")) && startupArguments != null)
{
if (s_args == null) s_args = startupArguments;
else
{
for (String arg : startupArguments)
{
if ((arg.startsWith("s:") || arg.startsWith("solution:")) &&
arg.substring(arg.indexOf(":")).equals(getPreferedSolutionNameToLoadOnInit()))
{
s_args = startupArguments;
break;
}
}
}
}
}
boolean autoSaveBlocked = false;
if (foundSetManager != null) // always call stopEditing, also when ERL is not editing so that prepareForSave is always called
{
// close solution is not a javaScript stop (do not save edited records if autoSave is off)
int stopEditing = foundSetManager.getEditRecordList().stopEditing(false);
if (stopEditing != ISaveConstants.AUTO_SAVE_BLOCKED || foundSetManager.getEditRecordList().getAutoSave() == true)
{
// so the stopEditing was not blocked because autoSave is off
if (stopEditing != ISaveConstants.STOPPED)
{
if (force)
{
// just clean everything when in force mode
foundSetManager.getEditRecordList().init();
}
else
{
return false;
}
}
}
else
{
// stopEditing was blocked because autoSave is off; this means unsaved changes will not be saved automatically
// when this solution closes; however we must give the user the opportunity to save unsaved data on his solution close
// handler - so we will clear edited records only after that method is called - and if the closing of the solution continues
autoSaveBlocked = true;
}
}
handleArguments(s_args);
if (!callCloseSolutionMethod(force) && !force)
{
return false;
}
solutionClosed = true;
if (autoSaveBlocked && foundSetManager != null)
{
// clear edited records so that they will not be auto-saved by the operations that follow
foundSetManager.getEditRecordList().init();
}
checkForActiveTransactions(force);
// formmanager does a savedata on visible form
J2DBGlobals.firePropertyChange(this, "solution", solutionRoot.getSolution(), null); //$NON-NLS-1$
solutionRoot.clearSecurityAccess();
saveSolution();// do save after firePropertyChange because that may flush some changes (from undoable cmds)
try
{
solutionRoot.close(getActiveSolutionHandler());
}
catch (Exception e)
{
// ignore any error
Debug.error(e);
}
// Notify server!
IClientHost ch = getClientHost();
if (ch != null)// can be null if failed to init
{
try
{
if (clientInfo != null)
{
clientInfo.setOpenSolutionId(-1);
ch.pushClientInfo(clientInfo.getClientId(), clientInfo);
}
}
catch (Exception e1)
{
Debug.error(e1);// incase connection to server is dead
}
}
if (foundSetManager != null)
{
foundSetManager.flushCachedItems();// delete any foundsets
foundSetManager.init();
}
// inform messages of the closed solution
refreshI18NMessages();
if (scriptEngine != null)
{
scriptEngine.destroy();
scriptEngine = null;// delete current script engine
}
// drop any temp tables for this client
IDataServer ds = getDataServer();
if (ds != null)
{
ds.dropTemporaryTable(getClientID(), null, null);
}
}
catch (Exception ex)
{
Debug.error(ex);
}
finally
{
isClosing = false;
if (solutionClosed && dataServer instanceof DataServerProxy) dataServer = ((DataServerProxy)dataServer).getEnclosingDataServer();
// just set the solutionClosed boolean to false again here, now the solution should be null.
solutionClosed = false;
}
return true;
}
/**
* Call the on-close0solution method (if defined)
*
* @param force passed onto method
* @return
*/
protected boolean callCloseSolutionMethod(boolean force)
{
ScriptMethod sm = null;
try
{
sm = getFlattenedSolution().getScriptMethod(getSolution().getOnCloseMethodID());
}
catch (Exception e)
{
// ignore any error
Debug.error(e);
}
if (sm != null)
{
try
{
return !Boolean.FALSE.equals(getScriptEngine().getSolutionScope().getScopesScope().executeGlobalFunction(
sm.getScopeName(),
sm.getName(),
Utils.arrayMerge((new Object[] { Boolean.valueOf(force) }),
Utils.parseJSExpressions(getSolution().getInstanceMethodArguments("onCloseMethodID"))), false, false)); //$NON-NLS-1$
}
catch (Exception e1)
{
reportError(Messages.getString("servoy.client.error.executing.method", new Object[] { sm.getName() }), e1); //$NON-NLS-1$
}
}
return true;
}
protected abstract void checkForActiveTransactions(boolean force);
public abstract boolean saveSolution();
public IPluginManagerInternal getPluginManager()
{
return pluginManager;
}
protected abstract void createPluginManager();
protected transient IPluginAccess pluginAccess;
public IPluginAccess getPluginAccess()
{
return pluginAccess;
}
public abstract void refreshI18NMessages();
protected void registerListeners()
{
J2DBGlobals.addPropertyChangeListener(this, formManager);
J2DBGlobals.addPropertyChangeListener(modeManager, formManager);
}
protected void unRegisterListeners()
{
J2DBGlobals.removeAllPropertyChangeListeners(this);
J2DBGlobals.removeAllPropertyChangeListeners(modeManager);
}
private void writeObject(@SuppressWarnings("unused")
ObjectOutputStream stream)
{
//serialize is not implemented
}
private void readObject(@SuppressWarnings("unused")
ObjectInputStream stream)
{
//serialize is not implemented
}
protected transient volatile IActiveSolutionHandler activeSolutionHandler;
public IActiveSolutionHandler getActiveSolutionHandler()
{
if (activeSolutionHandler == null)
{
synchronized (this)
{
if (activeSolutionHandler == null)
{
activeSolutionHandler = createActiveSolutionHandler();
}
}
}
return activeSolutionHandler;
}
/**
* This method is intended to be overridden by clients that access the server over rmi
*/
protected IActiveSolutionHandler createActiveSolutionHandler()
{
return new LocalActiveSolutionHandler(getApplicationServer(), this);
}
public void clearLoginForm()
{
try
{
// try to push the new client info
getClientHost().pushClientInfo(clientInfo.getClientId(), clientInfo);
loadSecuritySettings(solutionRoot);
getFormManager().clearLoginForm();
}
catch (Exception ex)
{
Solution s = solutionRoot.getSolution();
reportError(Messages.getString("servoy.client.error.loadingsolution", new Object[] { (s != null ? s.getName() : "<unknown>") }), ex); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* @param useLoginSolution the useLoginSolution to set
*/
public void setUseLoginSolution(boolean useLoginSolution)
{
this.useLoginSolution = useLoginSolution;
}
public boolean loadSolutionsAndModules(SolutionMetaData solutionMetaData)
{
if (solutionRoot.getSolution() != null && !solutionRoot.getSolution().getName().equals(solutionMetaData.getName())) return false; // SHOULD BE NULL!
try
{
if (solutionMetaData != null)
{
if (!useLoginSolution && solutionMetaData.getMustAuthenticate())
{
// must login the old fashioned way
showDefaultLogin();
if (clientInfo.getUserUid() == null)
{
return false;
}
}
boolean loadLoginSolution = useLoginSolution && clientInfo.getUserUid() == null;
solutionRoot.setSolution(solutionMetaData, loadLoginSolution, !loadLoginSolution, getActiveSolutionHandler());// assign only here and not earlier
if (solutionRoot.getSolution() == null && clientInfo.getUserUid() == null)
{
// no login solution, use default servoy login
if (haveRepositoryAccess())
{
// Have repository access, don't need authorised access
solutionRoot.setSolution(solutionMetaData, false, true, getActiveSolutionHandler());
if (solutionMetaData.getMustAuthenticate() && clientInfo.getUserUid() == null && solutionRoot.getSolution() != null &&
solutionRoot.getSolution().getLoginFormID() <= 0)
{
// must login the old fashioned way
showDefaultLogin();
if (clientInfo.getUserUid() == null)
{
return false;
}
}
}
else
{
showDefaultLogin();
if (clientInfo.getUserUid() == null)
{
return false;
}
}
}
if (solutionRoot.getSolution() == null)
{
reportError(Messages.getString("servoy.client.error.loadingsolution", new Object[] { (solutionMetaData.getName()) }), null); //$NON-NLS-1$
return false;
}
solutionLoaded(getSolution());
}
return true;
}
catch (Exception ex)
{
Debug.error(ex);
if (ex instanceof UnmarshalException && ex.getMessage().indexOf("java.io.NotSerializableException: com.servoy.j2db.server.persistence.Server") >= 0)
{
// this happens when the repository server is used in a solution while user tables in repository is disabled
if (dataServer != null)
{
try
{
// log on the server
dataServer.logMessage("Client could not load solution " + solutionMetaData.getName() +
", probably because repository server is used, see admin setting " + Settings.ALLOW_CLIENT_REPOSITORY_ACCESS_SETTING);
}
catch (RemoteException e)
{
// bummer
}
}
reportError(Messages.getString("servoy.foundSet.error.noAccess"), ex); //$NON-NLS-1$
}
else
{
reportError(Messages.getString(
"servoy.client.error.loadingsolution", new Object[] { (solutionMetaData != null ? solutionMetaData.getName() : "<unknown>") }), ex); //$NON-NLS-1$ //$NON-NLS-2$
}
return false;
}
}
protected abstract void showDefaultLogin() throws ServoyException;
public void loadSecuritySettings(FlattenedSolution root) throws ServoyException, RemoteException
{
if (clientInfo.getUserUid() != null)
{
Solution sol = root.getSolution();
String[] groups = clientInfo.getUserGroups();
if (groups == null) //fall back on retrieval of groups based on user_uid
{
groups = getUserManager().getUserGroups(clientInfo.getClientId(), clientInfo.getUserUid());
}
root.clearSecurityAccess();
int[] sids = new int[] { sol.getSolutionID() };
int[] srns = new int[] { sol.getReleaseNumber() };
Solution[] modules = root.getModules();
if (modules != null)
{
sids = new int[modules.length + 1];
sids[0] = sol.getSolutionID();
srns = new int[modules.length + 1];
srns[0] = sol.getReleaseNumber();
for (int i = 0; i < modules.length; i++)
{
Solution module = modules[i];
sids[i + 1] = module.getSolutionID();
srns[i + 1] = module.getReleaseNumber();
}
}
Map<Object, Integer> securityAccess = getUserManager().getSecurityAccess(clientInfo.getClientId(), sids, srns, groups);
root.addSecurityAccess(securityAccess);
if (foundSetManager != null)
{
((FoundSetManager)foundSetManager).flushSecuritySettings();
}
}
}
protected void solutionLoaded(Solution s)
{
try
{
loadSecuritySettings(solutionRoot);
refreshI18NMessages();
getScriptEngine().getScopesScope().reloadVariablesAndScripts(); // add variables for new solution
// These lines must be before other solutionLoaded call implementations, because a long running process
// (solution startup method) will never update the status.
getClientInfo().setOpenSolutionId(s.getSolutionMetaData().getRootObjectId());
getClientInfo().setOpenSolutionTimestamp(System.currentTimeMillis());
getClientHost().pushClientInfo(getClientInfo().getClientId(), getClientInfo());
}
catch (RemoteException e)
{
Debug.error(e);
}
catch (ServoyException e)
{
Debug.error(e);
}
}
//server-to-desktop activation
public abstract void activateSolutionMethod(String globalMethodName, StartupArguments argumentsScope);
public synchronized DataServerProxy getDataServerProxy()
{
IDataServer ds = getDataServer();
if (ds != null && !(ds instanceof DataServerProxy))
{
dataServer = new DataServerProxy(ds);
ds = dataServer;
}
return (DataServerProxy)ds;
}
private transient boolean isHandlingError = false;
public void handleException(String servoyMsg, final Exception e)
{
// Ignore the ExitScriptException completely.
if (e instanceof ExitScriptException || e.getCause() instanceof ExitScriptException ||
(e instanceof JavaScriptException && ((JavaScriptException)e).getValue() instanceof ExitScriptException)) return;
// If the given message is null then let it be the localized message of the deepest cause (the real cause)
String msg = servoyMsg;
if (msg == null)
{
Throwable t = e;
while (t.getCause() != null)
{
t = t.getCause();
}
msg = t.getLocalizedMessage();
}
Solution s = getSolution();
if (s != null)// && (e instanceof ApplicationException || e instanceof DataException || e instanceof JavaScriptException))
{
Object thrown = getScriptException(e);
if (!testClientRegistered(thrown))
{
return;
}
ScriptMethod sm = null;
int mid = s.getOnErrorMethodID();
if (mid > 0)
{
sm = getFlattenedSolution().getScriptMethod(mid);
}
if (sm == null || isHandlingError)//check for error handler, or when a error ocurs in error handler
{
reportError(msg, e);
}
else
{
try
{
isHandlingError = true;
Object retval = getScriptEngine().getScopesScope().executeGlobalFunction(sm.getScopeName(), sm.getName(),
Utils.arrayMerge((new Object[] { thrown }), Utils.parseJSExpressions(s.getInstanceMethodArguments("onErrorMethodID"))), //$NON-NLS-1$
false, false);
if (Utils.getAsBoolean(retval))
{
reportError(msg, e);//error handler cannot handle this error
}
}
catch (Exception e1)
{
reportError(msg, e);
}
finally
{
isHandlingError = false;
}
}
}
else
//no solution
{
reportError(msg, e);
}
}
public static Object getScriptException(final Exception e)
{
Exception scriptException = e;
//verify whether e is not caused by a ServoyException (at runtime, exceptions thrown are wrapped in WrappedException,
// so we need to look for a ServoyException into the chain)
// first check for a javascript exception with its value
if (scriptException instanceof JavaScriptException)
{
if (((JavaScriptException)scriptException).getValue() instanceof Exception)
{
scriptException = (Exception)((JavaScriptException)scriptException).getValue();
}
else if (((JavaScriptException)scriptException).getValue() != null)
{
// just return the object thrown in scripting
return ((JavaScriptException)scriptException).getValue();
}
}
// then check if it is RhinoException and skip that one by default.
else if (scriptException instanceof RhinoException && scriptException.getCause() instanceof Exception)
{
scriptException = (Exception)scriptException.getCause();
}
// Now search for a ServoyException in the chain.
Throwable cause = scriptException;
while (cause != null && !(cause instanceof ServoyException))
{
cause = cause.getCause();
if (cause instanceof ServoyException)
{
scriptException = (ServoyException)cause;
}
}
return scriptException;
}
/**
* @param exception The exception that should be tested.
* @return true if the client is still just registered, false if the exception reports an unregistered client
*
*/
protected boolean testClientRegistered(Object exception)
{
return true;
}
public final boolean isInDeveloper()
{
return ApplicationServerRegistry.get() != null && ApplicationServerRegistry.get().isDeveloperStartup();
}
public abstract void blockGUI(String reason);
public abstract void releaseGUI();
public final void invokeLater(Runnable r)
{
try
{
doInvokeLater(r);
}
catch (Exception t)
{
// The caller never expects invokeLater to fail, even when the run() method is just called in the current thread
Debug.error(t);
}
}
protected abstract void doInvokeLater(Runnable r);
public final void invokeLater(Runnable r, boolean immediate)
{
try
{
doInvokeLater(r, immediate);
}
catch (Exception t)
{
// The caller never expects invokeLater to fail, even when the run() method is just called in the current thread
Debug.error(t);
}
}
protected void doInvokeLater(Runnable r, @SuppressWarnings("unused")
boolean immediate)
{
doInvokeLater(r);
}
/**
* @param name
* @param displayValues
* @param realValues
* @param autoconvert
*/
public void setValueListItems(String name, Object[] displayValues, Object[] realValues, boolean autoconvert)
{
ValueList vl = getFlattenedSolution().getValueList(name);
if (vl != null && vl.getValueListType() == IValueListConstants.CUSTOM_VALUES)
{
// TODO should getValueListItems not specify type and format??
IValueList valuelist = ComponentFactory.getRealValueList(this, vl, false, Types.OTHER, null, null);
if (valuelist instanceof CustomValueList)
{
int guessedType = Types.OTHER;
if (autoconvert && realValues != null)
{
guessedType = guessValuelistType(realValues);
}
else if (autoconvert && displayValues != null)
{
guessedType = guessValuelistType(displayValues);
}
if (guessedType != Types.OTHER)
{
((CustomValueList)valuelist).setValueType(guessedType);
}
((CustomValueList)valuelist).fillWithArrayValues(displayValues, realValues);
IBasicFormManager fm = getFormManager();
List<IFormController> cachedFormControllers = fm.getCachedFormControllers();
for (IFormController form : cachedFormControllers)
{
form.refreshView();
}
}
}
}
protected int guessValuelistType(Object[] realValues)
{
if (realValues == null)
{
return Types.OTHER;
}
//try to make number object in realValues, do content type guessing
int entries = 0;
for (int i = 0; i < realValues.length; i++)
{
if (realValues[i] == null)
{
continue;
}
if ((realValues[i] instanceof Number) || !Utils.equalObjects(Long.valueOf(Utils.getAsLong(realValues[i])), realValues[i]))
{
return Types.OTHER;
}
entries++;
}
if (entries == 0)
{
// nothing found to base the guess on
return Types.OTHER;
}
// all non-null elements can be interpreted as numbers
return IColumnTypes.INTEGER;
}
}