package com.servoy.j2db.server.ngclient;
import java.awt.Dimension;
import java.awt.print.PageFormat;
import java.io.IOException;
import java.net.URL;
import java.rmi.RemoteException;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.json.JSONArray;
import org.json.JSONObject;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.sablo.IChangeListener;
import org.sablo.eventthread.WebsocketSessionWindows;
import org.sablo.specification.WebObjectApiDefinition;
import org.sablo.specification.WebObjectSpecification;
import org.sablo.specification.WebServiceSpecProvider;
import org.sablo.websocket.CurrentWindow;
import org.sablo.websocket.IServerService;
import org.sablo.websocket.WebsocketSessionManager;
import com.servoy.base.persistence.constants.IValueListConstants;
import com.servoy.j2db.ApplicationException;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.IBasicFormManager;
import com.servoy.j2db.IDataRendererFactory;
import com.servoy.j2db.IFormController;
import com.servoy.j2db.IServiceProvider;
import com.servoy.j2db.J2DBGlobals;
import com.servoy.j2db.Messages;
import com.servoy.j2db.dataprocessing.CustomValueList;
import com.servoy.j2db.dataprocessing.IUserClient;
import com.servoy.j2db.dataprocessing.IValueList;
import com.servoy.j2db.dataprocessing.SwingFoundSetFactory;
import com.servoy.j2db.persistence.RepositoryException;
import com.servoy.j2db.persistence.Solution;
import com.servoy.j2db.persistence.SolutionMetaData;
import com.servoy.j2db.persistence.ValueList;
import com.servoy.j2db.plugins.IClientPluginAccess;
import com.servoy.j2db.plugins.IMediaUploadCallback;
import com.servoy.j2db.scripting.IExecutingEnviroment;
import com.servoy.j2db.scripting.PluginScope;
import com.servoy.j2db.server.headlessclient.AbstractApplication;
import com.servoy.j2db.server.ngclient.MediaResourcesServlet.MediaInfo;
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.server.shared.ApplicationServerRegistry;
import com.servoy.j2db.server.shared.IApplicationServer;
import com.servoy.j2db.server.shared.WebCredentials;
import com.servoy.j2db.ui.ItemFactory;
import com.servoy.j2db.util.Ad;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.IGetStatusLine;
import com.servoy.j2db.util.RendererParentWrapper;
import com.servoy.j2db.util.SecuritySupport;
import com.servoy.j2db.util.ServoyException;
import com.servoy.j2db.util.ServoyScheduledExecutor;
import com.servoy.j2db.util.Settings;
import com.servoy.j2db.util.Utils;
// TODO we should add a subclass between ClientState and SessionClient, (remove all "session" and wicket related stuff out of SessionClient)
// then we can extend that one.
@SuppressWarnings("nls")
public class NGClient extends AbstractApplication implements INGApplication, IChangeListener, IServerService, IGetStatusLine
{
private static final long serialVersionUID = 1L;
private final INGClientWebsocketSession wsSession;
private transient volatile ServoyScheduledExecutor scheduledExecutorService;
private volatile NGRuntimeWindowManager runtimeWindowManager;
private Map<Object, Object> uiProperties;
private final Map<String, String> overrideStyleSheets = new HashMap<String, String>();
public static final String APPLICATION_SERVICE = "$applicationService";
public static final String APPLICATION_SERVER_SERVICE = "applicationServerService";
public NGClient(INGClientWebsocketSession wsSession) throws Exception
{
super(new WebCredentials());
this.wsSession = wsSession;
getWebsocketSession().registerServerService(APPLICATION_SERVER_SERVICE, this);
getWebsocketSession().registerServerService(I18NService.NAME, new I18NService(this));
settings = Settings.getInstance();
getClientInfo().setApplicationType(getApplicationType());
try
{
applicationSetup();
applicationInit();
applicationServerInit();
}
catch (Exception e)
{
// if exception directly do a shutdown, so that this client doesn't hang.
try
{
shutDown(true);
}
catch (Exception e2)
{
Debug.error("Cannot shutdown properly after client init failed", e2);
}
throw e;
}
}
@Override
protected SolutionMetaData selectSolutionToLoad() throws RepositoryException
{
// don't return here the current solution, that should only be loaded really through
// a request == websocket endpoint
return null;
}
@Override
public void reportInfo(final String message)
{
Runnable runnable = new Runnable()
{
@Override
public void run()
{
try
{
getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeServiceCall("showMessage",
new Object[] { Utils.stringReplace(message, "\r", "") });
}
catch (IOException e)
{
Debug.error("Error sending message to client", e);
}
}
};
// make sure we report this on all windows.
if (CurrentWindow.exists() && CurrentWindow.get() instanceof WebsocketSessionWindows)
{
runnable.run();
}
else
{
CurrentWindow.runForWindow(new NGClientWebsocketSessionWindows(getWebsocketSession()), runnable);
}
}
@Override
public void overrideStyleSheet(String oldStyleSheet, String newStyleSheet)
{
overrideStyleSheets.put(oldStyleSheet, newStyleSheet);
Runnable runnable = new Runnable()
{
@Override
public void run()
{
getWebsocketSession().sendStyleSheet();
}
};
// make sure we report this on all windows.
if (CurrentWindow.exists() && CurrentWindow.get() instanceof WebsocketSessionWindows)
{
runnable.run();
}
else
{
CurrentWindow.runForWindow(new NGClientWebsocketSessionWindows(getWebsocketSession()), runnable);
}
}
/**
* @return the styleSheet
*/
public Map<String, String> getOverrideStyleSheets()
{
return overrideStyleSheets;
}
@Override
public void setLocale(Locale l)
{
boolean send = locale != null && !locale.equals(l);
super.setLocale(l);
if (send) getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeAsyncServiceCall("setLocale",
new Object[] { l.getLanguage(), l.getCountry() });
}
@Override
protected IExecutingEnviroment createScriptEngine()
{
IExecutingEnviroment scriptEngine = super.createScriptEngine();
WebObjectSpecification[] serviceSpecifications = WebServiceSpecProvider.getInstance().getAllWebServiceSpecifications();
PluginScope scope = (PluginScope)scriptEngine.getSolutionScope().get("plugins", scriptEngine.getSolutionScope());
scope.setLocked(false);
for (WebObjectSpecification serviceSpecification : serviceSpecifications)
{
scope.put(serviceSpecification.getName(), scope, new WebServiceScriptable(this, serviceSpecification, scriptEngine.getSolutionScope()));
}
scope.setLocked(true);
return scriptEngine;
}
@Override
public Locale getLocale()
{
if (locale == null) initLocaleAndTimeZone();
return super.getLocale();
}
@Override
public TimeZone getTimeZone()
{
if (timeZone == null) initLocaleAndTimeZone();
return super.getTimeZone();
}
private void initLocaleAndTimeZone()
{
Object retValue = null;
try
{
retValue = this.getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeServiceCall("getUtcOffsetsAndLocale", null);
}
catch (IOException e)
{
Debug.warn(e);
return;
}
if (retValue instanceof JSONObject)
{
String userAgent = ((JSONObject)retValue).optString("userAgent");
if (userAgent != null)
{
getClientInfo().addInfo("useragent:" + userAgent);
}
String platform = ((JSONObject)retValue).optString("platform");
if (platform != null)
{
getClientInfo().addInfo("platform:" + platform);
}
if (timeZone == null)
{
String utc = ((JSONObject)retValue).optString("utcOffset");
if (utc != null)
{
// apparently it is platform dependent on whether you get the
// offset in a decimal form or not. This parses the decimal
// form of the UTC offset, taking into account several
// possibilities
// such as getting the format in +2.5 or -1.2
int dotPos = utc.indexOf('.');
if (dotPos >= 0)
{
String hours = utc.substring(0, dotPos);
String hourPart = utc.substring(dotPos + 1);
if (hours.startsWith("+"))
{
hours = hours.substring(1);
}
int offsetHours = Integer.parseInt(hours);
int offsetMins = (int)(Double.parseDouble(hourPart) * 6);
// construct a GMT timezone offset string from the retrieved
// offset which can be parsed by the TimeZone class.
AppendingStringBuffer sb = new AppendingStringBuffer("GMT");
sb.append(offsetHours > 0 ? "+" : "-");
sb.append(Math.abs(offsetHours));
sb.append(":");
if (offsetMins < 10)
{
sb.append("0");
}
sb.append(offsetMins);
timeZone = TimeZone.getTimeZone(sb.toString());
}
else
{
int offset = Integer.parseInt(utc);
if (offset < 0)
{
utc = utc.substring(1);
}
timeZone = TimeZone.getTimeZone("GMT" + ((offset > 0) ? "+" : "-") + utc);
}
String dstOffset = ((JSONObject)retValue).optString("utcDstOffset");
if (timeZone != null && dstOffset != null)
{
TimeZone dstTimeZone = null;
dotPos = dstOffset.indexOf('.');
if (dotPos >= 0)
{
String hours = dstOffset.substring(0, dotPos);
String hourPart = dstOffset.substring(dotPos + 1);
if (hours.startsWith("+"))
{
hours = hours.substring(1);
}
int offsetHours = Integer.parseInt(hours);
int offsetMins = (int)(Double.parseDouble(hourPart) * 6);
// construct a GMT timezone offset string from the
// retrieved
// offset which can be parsed by the TimeZone class.
AppendingStringBuffer sb = new AppendingStringBuffer("GMT");
sb.append(offsetHours > 0 ? "+" : "-");
sb.append(Math.abs(offsetHours));
sb.append(":");
if (offsetMins < 10)
{
sb.append("0");
}
sb.append(offsetMins);
dstTimeZone = TimeZone.getTimeZone(sb.toString());
}
else
{
int offset = Integer.parseInt(dstOffset);
if (offset < 0)
{
dstOffset = dstOffset.substring(1);
}
dstTimeZone = TimeZone.getTimeZone("GMT" + ((offset > 0) ? "+" : "-") + dstOffset);
}
// if the dstTimezone (1 July) has a different offset then
// the real time zone (1 January) try to combine the 2.
if (dstTimeZone != null && dstTimeZone.getRawOffset() != timeZone.getRawOffset())
{
int dstSaving = dstTimeZone.getRawOffset() - timeZone.getRawOffset();
String[] availableIDs = TimeZone.getAvailableIDs(timeZone.getRawOffset());
for (String availableID : availableIDs)
{
TimeZone zone = TimeZone.getTimeZone(availableID);
if (zone.getDSTSavings() == dstSaving)
{
// this is a best guess... still the start and end of the DST should
// be needed to know to be completely correct, or better yet
// not just the GMT offset but the TimeZone ID should be transfered
// from the browser.
timeZone = zone;
break;
}
}
}
// if the timezone is really just the default of the server just use that one.
TimeZone dftZone = TimeZone.getDefault();
if (timeZone.getRawOffset() == dftZone.getRawOffset() && timeZone.getDSTSavings() == dftZone.getDSTSavings())
{
timeZone = dftZone;
}
}
}
}
if (locale == null)
{
String browserLocale = ((JSONObject)retValue).optString("locale");
if (browserLocale != null)
{
String[] languageAndCountry = browserLocale.split("-");
if (languageAndCountry.length == 1)
{
locale = new Locale(languageAndCountry[0]);
}
else if (languageAndCountry.length == 2)
{
locale = new Locale(languageAndCountry[0], languageAndCountry[1]);
}
getClientInfo().addInfo("locale:" + locale);
}
}
}
if (timeZone != null)
{
getClientInfo().setTimeZone(timeZone);
}
getClientInfo().addInfo("session uuid: " + getWebsocketSession().getUuid());
try
{
getClientHost().pushClientInfo(getClientInfo().getClientId(), getClientInfo());
}
catch (RemoteException e)
{
Debug.error(e);
}
}
public void loadSolution(String solutionName) throws RepositoryException
{
try
{
SolutionMetaData solutionMetaData = getApplicationServer().getSolutionDefinition(solutionName, getSolutionTypeFilter());
if (solutionMetaData == null)
{
throw new IllegalArgumentException(Messages.getString("servoy.exception.solutionNotFound", new Object[] { solutionName })); //$NON-NLS-1$
}
loadSolution(solutionMetaData);
}
catch (RemoteException e)
{
throw new RepositoryException(e);
}
}
@Override
protected int getSolutionTypeFilter()
{
return super.getSolutionTypeFilter() | SolutionMetaData.NG_CLIENT_ONLY;
}
@Override
public INGFormManager getFormManager()
{
return (INGFormManager)super.getFormManager();
}
// public synchronized Map<String, Map<String, Map<String, Object>>> getAllComponentsChanges()
// {
// Map<String, Map<String, Map<String, Object>>> changes = new HashMap<>(8);
// if (isShutDown()) return changes;
// for (IFormController fc : getFormManager().getCachedFormControllers())
// {
// if (fc.isFormVisible())
// {
// Map<String, Map<String, Object>> formChanges = ((WebFormUI)fc.getFormUI()).getAllComponentsChanges();
// if (formChanges.size() > 0)
// {
// changes.put(fc.getName(), formChanges);
// }
// }
// }
// return changes;
// }
@Override
protected void solutionLoaded(Solution s)
{
super.solutionLoaded(s);
getWebsocketSession().solutionLoaded(s);
}
@Override
public INGClientWebsocketSession getWebsocketSession()
{
return wsSession;
}
@Override
public void valueChanged()
{
getWebsocketSession().valueChanged();
}
@Override
protected void doInvokeLater(Runnable r)
{
wsSession.getEventDispatcher().postEvent(r);
}
@Override
public boolean isEventDispatchThread()
{
return wsSession.getEventDispatcher().isEventDispatchThread();
}
@Override
public void invokeAndWait(Runnable r)
{
FutureTask<Object> future = new FutureTask<Object>(r, null);
wsSession.getEventDispatcher().addEvent(future);
try
{
future.get(); // blocking
}
catch (InterruptedException e)
{
Debug.trace(e);
}
catch (ExecutionException e)
{
e.getCause().printStackTrace();
Debug.error(e.getCause());
}
}
@Override
protected boolean startApplicationServerConnection()
{
try
{
applicationServer = ApplicationServerRegistry.getService(IApplicationServer.class);
return true;
}
catch (Exception ex)
{
reportError(Messages.getString("servoy.client.error.finding.dataservice"), ex); //$NON-NLS-1$
return false;
}
}
@Override
protected void loadSolution(SolutionMetaData solutionMeta) throws RepositoryException
{
if (loadSolutionsAndModules(solutionMeta))
{
J2DBGlobals.firePropertyChange(this, "solution", null, getSolution()); //$NON-NLS-1$
}
}
@Override
protected boolean callCloseSolutionMethod(final boolean force)
{
final boolean[] retValue = new boolean[1];
Runnable run = new Runnable()
{
@Override
public void run()
{
retValue[0] = doCallCloseSolutionMethod(force);
}
};
if (CurrentWindow.exists()) run.run();
else CurrentWindow.runForWindow(new NGClientWebsocketSessionWindows(getWebsocketSession()), run);
return retValue[0];
}
private boolean doCallCloseSolutionMethod(boolean force)
{
boolean canClose = super.callCloseSolutionMethod(force);
//cleanup here before script engine is destroyed
if (canClose || force)
{
WebObjectSpecification[] serviceSpecifications = WebServiceSpecProvider.getInstance().getAllWebServiceSpecifications();
for (WebObjectSpecification serviceSpecification : serviceSpecifications)
{
WebObjectApiDefinition apiFunction = serviceSpecification.getApiFunction("cleanup");
if (apiFunction != null && getScriptEngine() != null)
{
PluginScope scope = (PluginScope)getScriptEngine().getSolutionScope().get("plugins", getScriptEngine().getSolutionScope());
if (scope != null)
{
Scriptable service = (Scriptable)scope.get(serviceSpecification.getName(), null);
Object api = service.get(apiFunction.getName(), null);
if (api instanceof Function)
{
Context context = Context.enter();
try
{
((Function)api).call(context, scope, service, null);
}
catch (Exception ex)
{
Debug.error(ex);
}
finally
{
Context.exit();
}
}
}
}
}
}
return canClose;
}
@Override
public boolean closeSolution(boolean force, Object[] args)
{
String currentSolution = isSolutionLoaded() ? getSolutionName() : null;
boolean isCloseSolution = super.closeSolution(force, args);
if (isCloseSolution)
{
if (args == null || args.length < 1)
{
if (!force && showUrl == null)
{
CurrentWindow.runForWindow(new NGClientWebsocketSessionWindows(getWebsocketSession()), new Runnable()
{
public void run()
{
getWebsocketSession().getClientService(NGRuntimeWindowManager.WINDOW_SERVICE).executeAsyncServiceCall("reload", new Object[0]);
}
});
}
}
else
{
String openSolution = getPreferedSolutionNameToLoadOnInit();
if (openSolution == null) openSolution = currentSolution;
if (openSolution != null)
{
String m = getPreferedSolutionMethodNameToCall();
Object[] a = getPreferedSolutionMethodArguments();
StringBuilder url = new StringBuilder("solutions/").append(openSolution).append("/index.html");
if (m != null)
{
url.append("?m=").append(m);
if (a != null && a.length > 0) url.append("&a=").append(a[0]);
}
showURL(url.toString(), "_self", null, 0, true);
}
}
}
return isCloseSolution;
}
@Override
protected IBasicFormManager createFormManager()
{
return new NGFormManager(this);
}
@Override
public IChangeListener getChangeListener()
{
return this;
}
@Override
protected void createFoundSetManager()
{
foundSetManager = new NGFoundSetManager(this, new SwingFoundSetFactory());
foundSetManager.init();
}
@Override
public ScheduledExecutorService getScheduledExecutor()
{
if (scheduledExecutorService == null && !isShutDown())
{
synchronized (this)
{
if (scheduledExecutorService == null)
{
scheduledExecutorService = new ServoyScheduledExecutor(1, 4, 1)
{
private IServiceProvider prev;
@Override
protected void beforeExecute(Thread t, Runnable r)
{
super.beforeExecute(t, r);
prev = J2DBGlobals.getServiceProvider();
if (prev != NGClient.this)
{
// if this happens it is a webclient in developer..
// and the provider is not set for this web client. so it must be set.
J2DBGlobals.setServiceProvider(NGClient.this);
}
}
@Override
protected void afterExecute(Runnable r, Throwable t)
{
super.afterExecute(r, t);
J2DBGlobals.setServiceProvider(prev);
}
};
}
}
}
return scheduledExecutorService;
}
@SuppressWarnings("nls")
@Override
public Dimension getScreenSize()
{
try
{
Object retValue = this.getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeServiceCall("getScreenSize", null);
if (retValue instanceof JSONObject)
{
int orientation = ((JSONObject)retValue).optInt("orientation", 0);
int width = ((JSONObject)retValue).optInt("width", -1);
int height = ((JSONObject)retValue).optInt("height", -1);
if (orientation == 90 || orientation == -90)
{
return new Dimension(height, width);
}
return new Dimension(width, height);
}
}
catch (IOException e)
{
Debug.error(e);
}
return null;
}
@Override
public URL getServerURL()
{
try
{
Object retValue = this.getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeServiceCall("getLocation", null);
if (retValue instanceof String)
{
String url = (String)retValue;
int index = url.indexOf("/solutions/");
if (index != -1)
{
url = url.substring(0, index);
}
if (!url.toLowerCase().startsWith("http"))
{
url = "http://" + url;
}
return new URL(url);
}
}
catch (IOException e)
{
Debug.error(e);
}
return super.getServerURL();
}
@Override
public int getApplicationType()
{
return IApplication.NG_CLIENT;
}
@Override
public String getClientOSName()
{
try
{
Object retValue = this.getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeServiceCall("getUserAgentAndPlatform", null);
if (retValue instanceof JSONObject)
{
String userAgent = ((JSONObject)retValue).optString("userAgent");
if (userAgent != null)
{
if (userAgent.indexOf("NT 6.1") != -1) return "Windows 7";
if (userAgent.indexOf("NT 6.0") != -1) return "Windows Vista";
if (userAgent.indexOf("NT 5.1") != -1 || userAgent.indexOf("Windows XP") != -1) return "Windows XP";
if (userAgent.indexOf("Linux") != -1) return "Linux";
if (userAgent.indexOf("Mac") != -1) return "Mac OS";
}
String platform = ((JSONObject)retValue).optString("platform");
if (platform != null) return platform;
}
}
catch (IOException e)
{
Debug.error(e);
}
return super.getClientOSName();
}
@Override
public int getClientPlatform()
{
try
{
Object retValue = this.getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeServiceCall("getUserAgentAndPlatform", null);
if (retValue instanceof JSONObject)
{
String platform = ((JSONObject)retValue).optString("platform");
if (platform != null)
{
return Utils.getPlatform(platform);
}
}
}
catch (IOException e)
{
Debug.error(e);
}
return super.getClientPlatform();
}
@Override
public String getApplicationName()
{
return "Servoy NGClient";
}
@Override
public boolean putClientProperty(Object name, Object val)
{
if (IApplication.TABLEVIEW_NG_OPTIMIZED_READONLY_MODE.equals(name) || IApplication.TABLEVIEW_NG_PAGE_SIZE_FACTOR.equals(name))
{
getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeAsyncServiceCall("setUIProperty", new Object[] { name, val });
}
if (uiProperties == null)
{
uiProperties = new HashMap<Object, Object>();
}
uiProperties.put(name, val);
return true;
}
@Override
public Object getClientProperty(Object name)
{
return (uiProperties == null) ? null : uiProperties.get(name);
}
@Override
public void setTitle(String title)
{
getRuntimeWindowManager().getCurrentWindow().setTitle(title);
}
@Override
public ItemFactory getItemFactory()
{
// Not used in NGClient
return null;
}
@Override
public IDataRendererFactory getDataRenderFactory()
{
// Not used in NGClient
return null;
}
@Override
public RendererParentWrapper getPrintingRendererParent()
{
// Not used in NGClient
return null;
}
@Override
public PageFormat getPageFormat()
{
// Not used in NGClient
return null;
}
@Override
public void setPageFormat(PageFormat currentPageFormat)
{
// Not used in NGClient
}
@Override
public String getUserProperty(String name)
{
try
{
return (String)getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeServiceCall("getUserProperty", new Object[] { name });
}
catch (IOException e)
{
Debug.error("Error getting getting property '" + name + "'", e);
}
return null;
}
@Override
public void setUserProperty(String name, String value)
{
try
{
getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeServiceCall("setUserProperty", new Object[] { name, value });
}
catch (IOException e)
{
Debug.error("Error getting setting property '" + name + "' value: " + value, e);
}
}
@SuppressWarnings("nls")
@Override
public String[] getUserPropertyNames()
{
JSONArray result;
try
{
result = (JSONArray)getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeServiceCall("getUserPropertyNames", null);
String[] names = new String[result.length()];
for (int i = 0; i < names.length; i++)
{
names[i] = result.optString(i);
}
return names;
}
catch (IOException e)
{
Debug.error("Error getting user property names", e);
}
return new String[0];
}
@Override
public void looseFocus()
{
// TODO call request focus on a div in a client?
}
private ShowUrl showUrl = null;
@Override
public boolean showURL(String url, String target, String target_options, int timeout, boolean onRootFrame)
{
// 2 calls to show url? Just send this one.
if (showUrl != null)
{
this.getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeAsyncServiceCall("showUrl",
new Object[] { showUrl.url, showUrl.target, showUrl.target_options, Integer.valueOf(showUrl.timeout) });
}
showUrl = new ShowUrl(url, target, target_options, timeout, onRootFrame);
return true;
}
@Override
public void setStatusText(String text, String tooltip)
{
this.getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeAsyncServiceCall("setStatusText", new Object[] { text });
}
@Override
public NGRuntimeWindowManager getRuntimeWindowManager()
{
if (runtimeWindowManager == null)
{
synchronized (this)
{
if (runtimeWindowManager == null) runtimeWindowManager = new NGRuntimeWindowManager(this);
}
}
return runtimeWindowManager;
}
@Override
public synchronized void shutDown(boolean force)
{
super.shutDown(force);
if (isShutDown())
{
if (scheduledExecutorService != null)
{
scheduledExecutorService.shutdownNow();
try
{
scheduledExecutorService.awaitTermination(10, TimeUnit.SECONDS);
}
catch (InterruptedException e)
{
}
scheduledExecutorService = null;
}
if (showUrl == null) getWebsocketSession().sendRedirect(null);
WebsocketSessionManager.removeSession(getWebsocketSession().getUuid());
}
}
private transient Object[] adsInfo = null;//cache to expensive to get each time
@Override
protected boolean registerClient(IUserClient uc) throws Exception
{
boolean registered = false;
try
{
registered = super.registerClient(uc);
ApplicationServerRegistry.get().setServerProcess(getClientID());
if (!registered)
{
showInfoPanel();
}
}
catch (final ApplicationException e)
{
((NGClientWebsocketSession)wsSession).setClient(this);
CurrentWindow.runForWindow(new NGClientWebsocketSessionWindows(getWebsocketSession()), new Runnable()
{
@Override
public void run()
{
if (e.getErrorCode() == ServoyException.NO_LICENSE)
{
getWebsocketSession().getClientService("$sessionService").executeAsyncServiceCall("setNoLicense",
new Object[] { getLicenseAndMaintenanceDetail() });
}
else if (e.getErrorCode() == ServoyException.MAINTENANCE_MODE)
{
getWebsocketSession().getClientService("$sessionService").executeAsyncServiceCall("setMaintenanceMode",
new Object[] { getLicenseAndMaintenanceDetail() });
}
}
});
throw e;
}
return registered;
}
protected void showInfoPanel()
{
((NGClientWebsocketSession)wsSession).setClient(this);
invokeLater(new Runnable()
{
@Override
public void run()
{
if (adsInfo == null) adsInfo = Ad.getAdInfo();
final int w = Utils.getAsInteger(adsInfo[1]);
final int h = Utils.getAsInteger(adsInfo[2]);
if (w > 50 && h > 50)
{
final URL url = (URL)adsInfo[0];
final int t = Utils.getAsInteger(adsInfo[3]);
getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeAsyncServiceCall("showInfoPanel",
new Object[] { url.toString(), w, h, t, getI18NMessage("servoy.button.close") });
}
}
});
}
private Map<String, Object> getLicenseAndMaintenanceDetail()
{
Map<String, Object> detail = new HashMap<>();
String url = Settings.getInstance().getProperty("servoy.webclient.pageexpired.url");
if (url != null)
{
detail.put("redirectUrl", url);
String redirectTimeout = Settings.getInstance().getProperty("servoy.webclient.pageexpired.redirectTimeout");
detail.put("redirectTimeout", Utils.getAsInteger(redirectTimeout));
}
return detail;
}
@Override
public void setValueListItems(String name, Object[] displayValues, Object[] realValues, boolean autoconvert)
{
ValueList vl = getFlattenedSolution().getValueList(name);
if (vl != null && vl.getValueListType() == IValueListConstants.CUSTOM_VALUES)
{
int guessedType = Types.OTHER;
if (autoconvert && realValues != null)
{
guessedType = guessValuelistType(realValues);
}
else if (autoconvert && displayValues != null)
{
guessedType = guessValuelistType(displayValues);
}
IValueList valueList = com.servoy.j2db.component.ComponentFactory.getRealValueList(this, vl, true, Types.OTHER, null, null);
if (valueList instanceof CustomValueList)
{
((CustomValueList)valueList).setValueType(guessedType);
((CustomValueList)valueList).fillWithArrayValues(displayValues, realValues);
IBasicFormManager fm = getFormManager();
List<IFormController> cachedFormControllers = fm.getCachedFormControllers();
for (IFormController form : cachedFormControllers)
{
((WebFormController)form).getFormUI().refreshValueList(valueList);
}
}
}
}
@Override
public void showDefaultLogin() throws ServoyException
{
try
{
getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeServiceCall("showDefaultLogin", null);
}
catch (IOException ex)
{
Debug.error(ex);
}
}
private IMediaUploadCallback mediaUploadCallback;
public IMediaUploadCallback getMediaUploadCallback()
{
return mediaUploadCallback;
}
public void showFileOpenDialog(IMediaUploadCallback callback, boolean multiSelect, String dialogTitle)
{
try
{
mediaUploadCallback = callback;
String key = multiSelect ? "servoy.filechooser.upload.addFiles" : "servoy.filechooser.upload.addFile";
getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeServiceCall("showFileOpenDialog",
new Object[] { dialogTitle == null ? getI18NMessage(key) : dialogTitle, Boolean.valueOf(multiSelect) });
}
catch (IOException ex)
{
Debug.error(ex);
}
}
public String serveResource(String filename, byte[] bs, String mimetype, String contentDisposition)
{
MediaInfo mediaInfo = MediaResourcesServlet.createMediaInfo(bs, filename, mimetype, contentDisposition);
return "resources/" + MediaResourcesServlet.DYNAMIC_DATA_ACCESS + "/" + mediaInfo.getName();
}
/*
* @see org.sablo.websocket.IServerService#executeMethod(java.lang.String, org.json.JSONObject)
*/
@Override
public Object executeMethod(String methodName, JSONObject args) throws Exception
{
switch (methodName)
{
case "login" :
try
{
credentials.setUserName(args.optString("username"));
credentials.setPassword(args.optBoolean("encrypted") ? SecuritySupport.decrypt(Settings.getInstance(), args.optString("password"))
: args.optString("password"));
authenticate(null, null, new Object[] { credentials.getUserName(), credentials.getPassword() });
if (getClientInfo().getUserUid() != null)
{
wsSession.getEventDispatcher().postEvent(new Runnable()
{
public void run()
{
try
{
loadSolution(getSolution().getName());
}
catch (RepositoryException ex)
{
Debug.error(ex);
}
}
});
if (args.optBoolean("remember"))
{
JSONObject r = new JSONObject();
r.put("username", credentials.getUserName());
r.put("password", SecuritySupport.encrypt(Settings.getInstance(), credentials.getPassword()));
return r;
}
else return Boolean.TRUE;
}
}
catch (Exception ex)
{
Debug.error(ex);
}
return Boolean.FALSE;
case "autosave" :
getFoundSetManager().getEditRecordList().stopEditing(false);
break;
case "callServerSideApi" :
{
String serviceName = args.getString("service");
PluginScope scope = (PluginScope)getScriptEngine().getSolutionScope().get("plugins", getScriptEngine().getSolutionScope());
Object service = scope.get(serviceName, scope);
if (service instanceof WebServiceScriptable)
{
return ((WebServiceScriptable)service).executeScopeFunction(args.getString("methodName"), args.getJSONArray("args"));
}
else
{
Debug.warn("callServerSideApi for unknown service '" + serviceName + "'");
}
break;
}
}
return null;
}
@Override
public boolean isInDesigner()
{
return false;
}
@Override
protected IClientPluginAccess createClientPluginAccess()
{
return new NGClientPluginAccessProvider(this);
}
@Override
public void logout(final Object[] solution_to_open_args)
{
if (getClientInfo().getUserUid() != null)
{
boolean doLogoutAndClearUserInfo = false;
if (getSolution() != null)
{
boolean doLogOut = getClientInfo().getUserUid() != null;
if (getSolution() != null)
{
doLogOut = closeSolution(false, solution_to_open_args);
}
doLogoutAndClearUserInfo = doLogOut && getSolution() == null;
}
else
{
doLogoutAndClearUserInfo = true;
}
if (doLogoutAndClearUserInfo)
{
if (getApplicationServerAccess() != null && getClientID() != null)
{
try
{
getApplicationServerAccess().logout(getClientID());
}
catch (Exception ex)
{
Debug.log("Error during logout", ex);
}
}
credentials.clear();
getClientInfo().clearUserInfo();
}
}
}
@Override
public void updateUI(int time)
{
try
{
CurrentWindow.get().sendChanges();
}
catch (Exception ex)
{
Debug.error(ex);
}
}
@Override
public void changesWillBeSend()
{
if (showUrl != null)
{
this.getWebsocketSession().getClientService(NGClient.APPLICATION_SERVICE).executeAsyncServiceCall("showUrl",
new Object[] { showUrl.url, showUrl.target, showUrl.target_options, Integer.valueOf(showUrl.timeout) });
showUrl = null;
}
}
/**
* Get a status line to be displayed on the admin page.
*/
public String getStatusLine()
{
long lastAccessed = getWebsocketSession().getLastAccessed();
if (lastAccessed == Long.MIN_VALUE)
{
// this should normally not happen
return "No websockets";
}
long lastPingTime = getWebsocketSession().getLastPingTime();
if (lastPingTime > 0)
{
// a window is in use, there is a last ping time
return "Websocket connected, last ping time: " + new SimpleDateFormat("EEE HH:mm:ss").format(new Date(lastPingTime));
}
return "Websockets disconnected since " + new SimpleDateFormat("EEE HH:mm:ss").format(new Date(lastAccessed));
}
private class ShowUrl
{
private final String url;
private final String target;
private final String target_options;
private final int timeout;
private final boolean onRootFrame;
/**
* @param url
* @param target
* @param target_options
* @param timeout
* @param onRootFrame
*/
public ShowUrl(String url, String target, String target_options, int timeout, boolean onRootFrame)
{
this.url = url;
this.target = target;
this.target_options = target_options;
this.timeout = timeout;
this.onRootFrame = onRootFrame;
}
}
}