/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2015 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.server.ngclient;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeoutException;
import org.sablo.Container;
import org.sablo.WebComponent;
import org.sablo.eventthread.EventDispatcher;
import org.sablo.specification.PropertyDescription;
import org.sablo.specification.WebObjectApiDefinition;
import org.sablo.specification.property.IBrowserConverterContext;
import org.sablo.websocket.BaseWindow;
import org.sablo.websocket.CurrentWindow;
import org.sablo.websocket.IToJSONWriter;
import org.sablo.websocket.IWebsocketEndpoint;
import com.servoy.j2db.FlattenedSolution;
import com.servoy.j2db.dataprocessing.IDataServer;
import com.servoy.j2db.persistence.Form;
import com.servoy.j2db.persistence.Solution;
import com.servoy.j2db.server.ngclient.endpoint.INGClientWebsocketEndpoint;
import com.servoy.j2db.server.shared.IPerfomanceRegistry;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.Pair;
import com.servoy.j2db.util.UUID;
/**
* Sablo window for NGClient
*
* @author rgansevles
*
*/
public class NGClientWindow extends BaseWindow implements INGClientWindow
{
/**
* @param websocketSession
* @param windowUuid
* @param windowName
*/
public NGClientWindow(INGClientWebsocketSession websocketSession, String windowUuid, String windowName)
{
super(websocketSession, windowUuid, windowName);
}
public static INGClientWindow getCurrentWindow()
{
return (INGClientWindow)CurrentWindow.get();
}
@Override
public INGClientWebsocketSession getSession()
{
return (INGClientWebsocketSession)super.getSession();
}
@Override
public INGClientWebsocketEndpoint getEndpoint()
{
return (INGClientWebsocketEndpoint)super.getEndpoint();
}
public INGApplication getClient()
{
return getSession().getClient();
}
@Override
public Container getForm(String formName)
{
return (Container)getSession().getClient().getFormManager().getForm(formName).getFormUI();
}
@Override
public void sendChanges() throws IOException
{
if (getSession().getClient() != null) getSession().getClient().changesWillBeSend();
super.sendChanges();
}
@Override
protected Object invokeApi(WebComponent receiver, WebObjectApiDefinition apiFunction, Object[] arguments, PropertyDescription argumentTypes,
Map<String, Object> callContributions)
{
Map<String, Object> call = new HashMap<>();
if (callContributions != null) call.putAll(callContributions);
boolean delayedCall = isDelayedApiCall(receiver, apiFunction);
if (!delayedCall)
{
IWebFormController form = getSession().getClient().getFormManager().getForm(receiver.findParent(IWebFormUI.class).getName());
touchForm(form.getForm(), form.getName(), false);
}
if (receiver instanceof WebFormComponent && ((WebFormComponent)receiver).getComponentContext() != null)
{
ComponentContext componentContext = ((WebFormComponent)receiver).getComponentContext();
call.put("propertyPath", componentContext.getPropertyPath());
}
IPerfomanceRegistry perfRegistry = null;
try
{
perfRegistry = (getClient().getApplicationServerAccess() != null ? getClient().getApplicationServerAccess().getFunctionPerfomanceRegistry() : null);
}
catch (RemoteException e)
{
Debug.error(e);
}
Pair<UUID, UUID> perfId = null;
if (perfRegistry != null && perfRegistry.isEnabled() && !delayedCall) // so it is waiting for a response
perfId = perfRegistry.getPerformanceData(getClient().getSolutionName()).startSubAction(
receiver.getSpecification().getName() + "." + apiFunction.getName(), System.currentTimeMillis(),
apiFunction.getBlockEventProcessing() ? IDataServer.METHOD_CALL : IDataServer.METHOD_CALL_WAITING_FOR_USER_INPUT, getClient().getClientID());
try
{
// actual call
return super.invokeApi(receiver, apiFunction, arguments, argumentTypes, call);
}
finally
{
if (perfId != null) perfRegistry.getPerformanceData(getClient().getSolutionName()).endSubAction(perfId);
}
}
@Override
public void touchForm(Form form, String realInstanceName, boolean async)
{
if (form == null) return;
String formName = realInstanceName == null ? form.getName() : realInstanceName;
String formUrl = getRealFormURLAndSeeIfItIsACopy(form, formName, false).getLeft();
boolean nowSentToClient = getEndpoint().addFormIfAbsent(formName, formUrl);
if (nowSentToClient)
{
IWebFormUI formUI = (IWebFormUI)getForm(form.getName());
if (formUI.getParentContainer() == null)
{
String currentWindowName = getCurrentWindow().getName();
if (currentWindowName == null)
{
currentWindowName = getSession().getClient().getRuntimeWindowManager().getMainApplicationWindow().getName();
}
formUI.setParentWindowName(currentWindowName);
}
// form is not yet on the client, send over the controller
updateController(form, formName, !async, new FormHTMLAndJSGenerator(getSession().getClient(), form, formName));
Debug.debug("touchForm(" + async + ") - addFormIfAbsent: " + form.getName());
}
else
{
formUrl = getEndpoint().getFormUrl(formName);
Debug.debug("touchForm(" + async + ") - formAlreadyPresent: " + form.getName());
}
// if sync wait until we got response from client as it is loaded
if (!async)
{
if (!getEndpoint().isFormCreated(formName))
{
if (!nowSentToClient)
{
// this means a previous async touchForm already sent URL and JS code (updateController) to client, but the client form was not yet loaded (directives, scopes....)
// so probably a tabpanel or component that asked for it changed it's mind and no longer showed it; make sure it will show before waiting!
getSession().getClientService(NGRuntimeWindowManager.WINDOW_SERVICE).executeAsyncServiceCall("requireFormLoaded",
new Object[] { formName });
}
Debug.debug("touchForm(" + async + ") - will suspend: " + form.getName());
// really send the changes
try
{
sendChanges();
}
catch (IOException e)
{
Debug.error(e);
}
try
{
getSession().getEventDispatcher().suspend(formUrl, IWebsocketEndpoint.EVENT_LEVEL_SYNC_API_CALL, EventDispatcher.CONFIGURED_TIMEOUT);
}
catch (CancellationException e)
{
throw e; // full browser refresh while doing this?
}
catch (TimeoutException e)
{
throw new RuntimeException("Touch form realInstanceName (" + form.getName() + ") timed out.", e); // timeout... something went wrong; propagate this exception to calling code...
}
}
}
}
protected void updateController(Form form, final String realFormName, final boolean forceLoad, IFormHTMLAndJSGenerator formTemplateGenerator)
{
try
{
Pair<String, Boolean> urlAndCopyState = getRealFormURLAndSeeIfItIsACopy(form, realFormName, true);
final String realUrl = urlAndCopyState.getLeft();
boolean copy = urlAndCopyState.getRight().booleanValue();
boolean needsToGenerateTemplates = (copy || !Boolean.valueOf(System.getProperty("servoy.generateformscripts", "false")).booleanValue());
final String jsTemplate = (needsToGenerateTemplates ? formTemplateGenerator.generateJS() : "");
final String htmlTemplate = (needsToGenerateTemplates ? formTemplateGenerator.generateHTMLTemplate() : "");
// update endpoint URL if needed
String previousURL = getEndpoint().getFormUrl(realFormName);
String realURLWithoutSessionId = dropSessionIdFrom(realUrl);
if (previousURL != null && !realURLWithoutSessionId.equals(previousURL))
{
boolean wasFormCreated = getEndpoint().isFormCreated(realFormName);
getEndpoint().formDestroyed(realFormName);
getEndpoint().addFormIfAbsent(realFormName, realURLWithoutSessionId);
if (wasFormCreated) getEndpoint().markFormCreated(realFormName);
}
CurrentWindow.runForWindow(this, new Runnable()
{
@Override
public void run()
{
if (getSession().getClient().isEventDispatchThread() && forceLoad)
{
try
{
getSession().getClientService(NGRuntimeWindowManager.WINDOW_SERVICE).executeServiceCall("updateController",
new Object[] { realFormName, jsTemplate, realUrl, Boolean.valueOf(forceLoad), htmlTemplate });
}
catch (IOException e)
{
Debug.error(e);
}
}
else
{
getSession().getClientService(NGRuntimeWindowManager.WINDOW_SERVICE).executeAsyncServiceCall("updateController",
new Object[] { realFormName, jsTemplate, realUrl, Boolean.valueOf(forceLoad), htmlTemplate });
}
}
});
}
catch (IOException e)
{
Debug.error(e);
}
}
@Override
public boolean hasFormChangedSinceLastSendToClient(Form flattenedForm, String realName)
{
boolean changed = true;
String clientUsedFormURL = getEndpoint().getFormUrl(realName);
if (clientUsedFormURL != null)
{
changed = !clientUsedFormURL.equals(getRealFormURLAndSeeIfItIsACopy(flattenedForm, realName, false).getLeft());
}
return changed;
}
protected String dropSessionIdFrom(String realNewURL)
{
// drop the "?sessionId=...." or "&sessionId=..." when comparing cause those are not part of end-point kept URLs
return realNewURL.substring(0, realNewURL.indexOf("sessionId=") - 1);
}
protected Pair<String, Boolean> getRealFormURLAndSeeIfItIsACopy(Form form, String realFormName, boolean addSessionID)
{
FlattenedSolution fs = getSession().getClient().getFlattenedSolution();
Solution sc = fs.getSolutionCopy(false);
String realUrl = getDefaultFormURLStart(form, realFormName);
boolean copy = false;
if (sc != null && sc.getChild(form.getUUID()) != null)
{
realUrl = realUrl + "?lm:" + form.getLastModified() + (addSessionID ? "&sessionId=" + getSession().getUuid() : "");
copy = true;
}
else if (!form.getName().endsWith(realFormName))
{
realUrl = realUrl + "?lm:" + form.getLastModified() + (addSessionID ? "&sessionId=" + getSession().getUuid() : "");
}
else
{
realUrl = realUrl + (addSessionID ? "?sessionId=" + getSession().getUuid() : "");
}
return new Pair<String, Boolean>(realUrl, Boolean.valueOf(copy));
}
@Override
public void updateForm(Form form, String name, IFormHTMLAndJSGenerator formTemplateGenerator)
{
if (hasForm(name))
{
// if form was not sent to client, do not send now; this is just recreateUI
updateController(form, name, false, formTemplateGenerator);
}
}
@Override
public boolean hasForm(String realName)
{
return getEndpoint().getFormUrl(realName) != null;
}
protected String getDefaultFormURLStart(Form form, String name)
{
return "solutions/" + form.getSolution().getName() + "/forms/" + name + ".html";
}
public void destroyForm(String name)
{
getSession().getClientService(NGRuntimeWindowManager.WINDOW_SERVICE).executeAsyncServiceCall("destroyController", new Object[] { name });
// also remove it from the endpoint as a form that is on the client.
getEndpoint().formDestroyed(name);
}
public void formCreated(String formName)
{
String formUrl = getEndpoint().getFormUrl(formName);
if (formUrl != null)
{
synchronized (formUrl)
{
getEndpoint().markFormCreated(formName);
getSession().getEventDispatcher().resume(formUrl);
Debug.debug("formCreated(" + formUrl + "): " + formName);
}
}
}
@Override
protected boolean formLoaded(WebComponent component)
{
IWebFormUI parent = component.findParent(IWebFormUI.class);
if (parent == null) return false;
IWebFormController controller = parent.getController();
if (controller == null) return false;
String formName = controller.getName();
String formUrl = getEndpoint().getFormUrl(formName);
if (formUrl != null)
{
synchronized (formUrl)
{
return getEndpoint().isFormCreated(formName);
}
}
return false;
}
@Override
public Object executeServiceCall(String serviceName, String functionName, Object[] arguments, WebObjectApiDefinition apiFunction,
IToJSONWriter<IBrowserConverterContext> pendingChangesWriter, boolean blockEventProcessing) throws IOException
{
IPerfomanceRegistry perfRegistry = null;
try
{
perfRegistry = (getClient().getApplicationServerAccess() != null ? getClient().getApplicationServerAccess().getFunctionPerfomanceRegistry() : null);
}
catch (RemoteException e)
{
Debug.error(e);
}
Pair<UUID, UUID> perfId = null;
if (perfRegistry != null && perfRegistry.isEnabled())
perfId = perfRegistry.getPerformanceData(getClient().getSolutionName()).startSubAction(serviceName + "." + functionName, System.currentTimeMillis(),
(apiFunction == null || apiFunction.getBlockEventProcessing()) ? IDataServer.METHOD_CALL : IDataServer.METHOD_CALL_WAITING_FOR_USER_INPUT,
getClient().getClientID());
try
{
return super.executeServiceCall(serviceName, functionName, arguments, apiFunction, pendingChangesWriter, blockEventProcessing);
}
finally
{
if (perfId != null) perfRegistry.getPerformanceData(getClient().getSolutionName()).endSubAction(perfId);
}
}
}