@@ -143,6 +143,11 @@ public class ApplicationConfiguration implements EntryPoint { | |||
/*-{ | |||
return this.getConfig("versionInfo").applicationVersion; | |||
}-*/; | |||
private native String getUIDL() | |||
/*-{ | |||
return this.getConfig("uidl"); | |||
}-*/; | |||
} | |||
/** | |||
@@ -242,6 +247,16 @@ public class ApplicationConfiguration implements EntryPoint { | |||
id = appId; | |||
} | |||
/** | |||
* Gets the initial UIDL from the DOM, if it was provided during the init | |||
* process. | |||
* | |||
* @return | |||
*/ | |||
public String getUIDL() { | |||
return getJsoConfiguration(id).getUIDL(); | |||
} | |||
/** | |||
* @return true if the application is served by std. Vaadin servlet and is | |||
* considered to be the only or main content of the host page. |
@@ -125,6 +125,8 @@ public class ApplicationConnection { | |||
private int activeRequests = 0; | |||
protected boolean cssLoaded = false; | |||
/** Parameters for this application connection loaded from the web-page */ | |||
private ApplicationConfiguration configuration; | |||
@@ -191,10 +193,16 @@ public class ApplicationConnection { | |||
* failed to start. This ensures that the applications are started in order, | |||
* to avoid session-id problems. | |||
* | |||
* @return | |||
*/ | |||
public void start() { | |||
repaintAll(); | |||
String jsonText = configuration.getUIDL(); | |||
if (jsonText == null) { | |||
// inital UIDL not in DOM, request later | |||
repaintAll(); | |||
} else { | |||
// initial UIDL provided in DOM, continue as if returned by request | |||
handleJSONText(jsonText); | |||
} | |||
} | |||
private native void initializeTestbenchHooks( | |||
@@ -348,6 +356,20 @@ public class ApplicationConnection { | |||
return (activeRequests > 0); | |||
} | |||
public void incrementActiveRequests() { | |||
if (activeRequests < 0) { | |||
activeRequests = 1; | |||
} else { | |||
activeRequests++; | |||
} | |||
} | |||
public void decrementActiveRequests() { | |||
if (activeRequests > 0) { | |||
activeRequests--; | |||
} | |||
} | |||
private String getRepaintAllParameters() { | |||
// collect some client side data that will be sent to server on | |||
// initial uidl request | |||
@@ -496,7 +518,7 @@ public class ApplicationConnection { | |||
(new Timer() { | |||
@Override | |||
public void run() { | |||
activeRequests--; | |||
decrementActiveRequests(); | |||
doUidlRequest(uri, payload, synchronous); | |||
} | |||
}).schedule(delay); | |||
@@ -513,28 +535,10 @@ public class ApplicationConnection { | |||
return; | |||
} | |||
final Date start = new Date(); | |||
// for(;;);[realjson] | |||
final String jsonText = response.getText().substring(9, | |||
response.getText().length() - 1); | |||
final ValueMap json; | |||
try { | |||
json = parseJSONResponse(jsonText); | |||
} catch (final Exception e) { | |||
endRequest(); | |||
showCommunicationError(e.getMessage() | |||
+ " - Original JSON-text:" + jsonText); | |||
return; | |||
} | |||
VConsole.log("JSON parsing took " | |||
+ (new Date().getTime() - start.getTime()) + "ms"); | |||
if (applicationRunning) { | |||
handleReceivedJSONMessage(start, jsonText, json); | |||
} else { | |||
applicationRunning = true; | |||
handleWhenCSSLoaded(jsonText, json); | |||
} | |||
handleJSONText(jsonText); | |||
} | |||
}; | |||
@@ -558,6 +562,34 @@ public class ApplicationConnection { | |||
} | |||
/** | |||
* Handles received UIDL JSON text, parsing it, and passing it on to the | |||
* appropriate handlers, while logging timiing information. | |||
* | |||
* @param jsonText | |||
*/ | |||
private void handleJSONText(String jsonText) { | |||
final Date start = new Date(); | |||
final ValueMap json; | |||
try { | |||
json = parseJSONResponse(jsonText); | |||
} catch (final Exception e) { | |||
endRequest(); | |||
showCommunicationError(e.getMessage() + " - Original JSON-text:" | |||
+ jsonText); | |||
return; | |||
} | |||
VConsole.log("JSON parsing took " | |||
+ (new Date().getTime() - start.getTime()) + "ms"); | |||
if (applicationRunning) { | |||
handleReceivedJSONMessage(start, jsonText, json); | |||
} else { | |||
applicationRunning = true; | |||
handleWhenCSSLoaded(jsonText, json); | |||
} | |||
} | |||
/** | |||
* Sends an asynchronous UIDL request to the server using the given URI. | |||
* | |||
@@ -587,9 +619,7 @@ public class ApplicationConnection { | |||
protected void handleWhenCSSLoaded(final String jsonText, | |||
final ValueMap json) { | |||
int heightOfLoadElement = DOM.getElementPropertyInt(loadElement, | |||
"offsetHeight"); | |||
if (heightOfLoadElement == 0 && cssWaits < MAX_CSS_WAITS) { | |||
if (!isCSSLoaded() && cssWaits < MAX_CSS_WAITS) { | |||
(new Timer() { | |||
@Override | |||
public void run() { | |||
@@ -601,6 +631,7 @@ public class ApplicationConnection { | |||
+ "(.v-loading-indicator height == 0)"); | |||
cssWaits++; | |||
} else { | |||
cssLoaded = true; | |||
handleReceivedJSONMessage(new Date(), jsonText, json); | |||
if (cssWaits >= MAX_CSS_WAITS) { | |||
VConsole.error("CSS files may have not loaded properly."); | |||
@@ -608,6 +639,17 @@ public class ApplicationConnection { | |||
} | |||
} | |||
/** | |||
* Checks whether or not the CSS is loaded. By default checks the size of | |||
* the loading indicator element. | |||
* | |||
* @return | |||
*/ | |||
protected boolean isCSSLoaded() { | |||
return cssLoaded | |||
|| DOM.getElementPropertyInt(loadElement, "offsetHeight") != 0; | |||
} | |||
/** | |||
* Shows the communication error notification. | |||
* | |||
@@ -672,7 +714,7 @@ public class ApplicationConnection { | |||
} | |||
protected void startRequest() { | |||
activeRequests++; | |||
incrementActiveRequests(); | |||
requestStartTime = new Date(); | |||
// show initial throbber | |||
if (loadTimer == null) { | |||
@@ -700,11 +742,11 @@ public class ApplicationConnection { | |||
checkForPendingVariableBursts(); | |||
runPostRequestHooks(configuration.getRootPanelId()); | |||
} | |||
activeRequests--; | |||
decrementActiveRequests(); | |||
// deferring to avoid flickering | |||
Scheduler.get().scheduleDeferred(new Command() { | |||
public void execute() { | |||
if (activeRequests == 0) { | |||
if (!hasActiveRequest()) { | |||
hideLoadingIndicator(); | |||
} | |||
} | |||
@@ -785,12 +827,14 @@ public class ApplicationConnection { | |||
private void hideLoadingIndicator() { | |||
if (loadTimer != null) { | |||
loadTimer.cancel(); | |||
if (loadTimer2 != null) { | |||
loadTimer2.cancel(); | |||
loadTimer3.cancel(); | |||
} | |||
loadTimer = null; | |||
} | |||
if (loadTimer2 != null) { | |||
loadTimer2.cancel(); | |||
loadTimer3.cancel(); | |||
loadTimer2 = null; | |||
loadTimer3 = null; | |||
} | |||
if (loadElement != null) { | |||
DOM.setStyleAttribute(loadElement, "display", "none"); | |||
} |
@@ -14,6 +14,7 @@ import java.io.OutputStream; | |||
import java.io.OutputStreamWriter; | |||
import java.io.PrintWriter; | |||
import java.io.Serializable; | |||
import java.io.StringWriter; | |||
import java.lang.reflect.InvocationTargetException; | |||
import java.lang.reflect.Method; | |||
import java.security.GeneralSecurityException; | |||
@@ -94,7 +95,7 @@ public abstract class AbstractCommunicationManager implements | |||
.getLogger(AbstractCommunicationManager.class.getName()); | |||
private static final RequestHandler APP_RESOURCE_HANDLER = new ApplicationResourceHandler(); | |||
private static final AjaxPageHandler AJAX_PAGE_HANDLER = new AjaxPageHandler() { | |||
private final AjaxPageHandler ajaxPageHandler = new AjaxPageHandler() { | |||
@Override | |||
protected String getApplicationOrSystemProperty(WrappedRequest request, | |||
@@ -108,6 +109,11 @@ public abstract class AbstractCommunicationManager implements | |||
defaultValue); | |||
} | |||
@Override | |||
protected AbstractCommunicationManager getCommunicationManager() { | |||
return AbstractCommunicationManager.this; | |||
} | |||
}; | |||
/** | |||
@@ -203,7 +209,7 @@ public abstract class AbstractCommunicationManager implements | |||
*/ | |||
public AbstractCommunicationManager(Application application) { | |||
this.application = application; | |||
application.addRequestHandler(AJAX_PAGE_HANDLER); | |||
application.addRequestHandler(ajaxPageHandler); | |||
application.addRequestHandler(APP_RESOURCE_HANDLER); | |||
requireLocale(application.getLocale().toString()); | |||
} | |||
@@ -521,7 +527,7 @@ public abstract class AbstractCommunicationManager implements | |||
* Internally process a UIDL request from the client. | |||
* | |||
* This method calls | |||
* {@link #handleVariables(Request, Response, Callback, Application, Window)} | |||
* {@link #handleVariables(WrappedRequest, WrappedResponse, Callback, Application, Root)} | |||
* to process any changes to variables by the client and then repaints | |||
* affected components using {@link #paintAfterVariableChanges()}. | |||
* | |||
@@ -725,17 +731,7 @@ public abstract class AbstractCommunicationManager implements | |||
.getAttribute(WRITE_SECURITY_TOKEN_FLAG); | |||
if (writeSecurityTokenFlag != null) { | |||
String seckey = (String) request | |||
.getSessionAttribute(ApplicationConnection.UIDL_SECURITY_TOKEN_ID); | |||
if (seckey == null) { | |||
seckey = UUID.randomUUID().toString(); | |||
request.setSessionAttribute( | |||
ApplicationConnection.UIDL_SECURITY_TOKEN_ID, seckey); | |||
} | |||
outWriter.print("\"" + ApplicationConnection.UIDL_SECURITY_TOKEN_ID | |||
+ "\":\""); | |||
outWriter.print(seckey); | |||
outWriter.print("\","); | |||
outWriter.print(getSecurityKeyUIDL(request)); | |||
} | |||
writeUidlResponce(callback, repaintAll, outWriter, root, analyzeLayouts); | |||
@@ -746,6 +742,41 @@ public abstract class AbstractCommunicationManager implements | |||
} | |||
/** | |||
* Gets the security key (and generates one if needed) as UIDL. | |||
* | |||
* @param request | |||
* @return the security key UIDL or "" if the feature is turned off | |||
*/ | |||
public String getSecurityKeyUIDL(WrappedRequest request) { | |||
final String seckey = getSecurityKey(request); | |||
if (seckey != null) { | |||
return "\"" + ApplicationConnection.UIDL_SECURITY_TOKEN_ID | |||
+ "\":\"" + seckey + "\","; | |||
} else { | |||
return ""; | |||
} | |||
} | |||
/** | |||
* Gets the security key (and generates one if needed). | |||
* | |||
* @param request | |||
* @return the security key | |||
*/ | |||
protected String getSecurityKey(WrappedRequest request) { | |||
String seckey = null; | |||
seckey = (String) request | |||
.getSessionAttribute(ApplicationConnection.UIDL_SECURITY_TOKEN_ID); | |||
if (seckey == null) { | |||
seckey = UUID.randomUUID().toString(); | |||
request.setSessionAttribute( | |||
ApplicationConnection.UIDL_SECURITY_TOKEN_ID, seckey); | |||
} | |||
return seckey; | |||
} | |||
public void writeUidlResponce(Callback callback, boolean repaintAll, | |||
final PrintWriter outWriter, Root root, boolean analyzeLayouts) | |||
throws PaintException { | |||
@@ -1075,6 +1106,18 @@ public abstract class AbstractCommunicationManager implements | |||
p.removeListener(this); | |||
} | |||
/** | |||
* Returns false if the cross site request forgery protection is turned off. | |||
* | |||
* @param application | |||
* @return false if the XSRF is turned off, true otherwise | |||
*/ | |||
public boolean isXSRFEnabled(Application application) { | |||
return !"true" | |||
.equals(application | |||
.getProperty(AbstractApplicationServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION)); | |||
} | |||
/** | |||
* TODO document | |||
* | |||
@@ -1099,9 +1142,7 @@ public abstract class AbstractCommunicationManager implements | |||
// Security: double cookie submission pattern unless disabled by | |||
// property | |||
if (!"true" | |||
.equals(application2 | |||
.getProperty(AbstractApplicationServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION))) { | |||
if (isXSRFEnabled(application2)) { | |||
if (bursts.length == 1 && "init".equals(bursts[0])) { | |||
// init request; don't handle any variables, key sent in | |||
// response. | |||
@@ -1936,6 +1977,10 @@ public abstract class AbstractCommunicationManager implements | |||
WrappedResponse response, Application application) | |||
throws IOException { | |||
// if we do not yet have a currentRoot, it should be initialized | |||
// shortly, and we should send the initial UIDL | |||
boolean sendUIDL = Root.getCurrentRoot() == null; | |||
// TODO Handle npe if id has not been registered | |||
CombinedRequest combinedRequest = application | |||
.getCombinedRequest(request); | |||
@@ -1944,11 +1989,11 @@ public abstract class AbstractCommunicationManager implements | |||
Root root = application.getRootForRequest(combinedRequest); | |||
// Use the same logic as for determined roots | |||
String widgetset = AJAX_PAGE_HANDLER.getWidgetsetForRoot( | |||
String widgetset = ajaxPageHandler.getWidgetsetForRoot( | |||
combinedRequest, root); | |||
String theme = AJAX_PAGE_HANDLER.getThemeForRoot(combinedRequest, | |||
String theme = ajaxPageHandler.getThemeForRoot(combinedRequest, | |||
root); | |||
String themeUri = AJAX_PAGE_HANDLER.getThemeUri(theme, | |||
String themeUri = ajaxPageHandler.getThemeUri(theme, | |||
combinedRequest); | |||
// TODO These are not required if it was only the init of the root | |||
@@ -1959,6 +2004,19 @@ public abstract class AbstractCommunicationManager implements | |||
// Root id might have changed based on e.g. window.name | |||
params.put(ApplicationConnection.ROOT_ID_PARAMETER, | |||
root.getRootId()); | |||
if (sendUIDL) { | |||
// TODO maybe unify w/ AjaxPageHandler & writeUidlResponCe()? | |||
this.makeAllPaintablesDirty(root); | |||
StringWriter sWriter = new StringWriter(); | |||
PrintWriter pWriter = new PrintWriter(sWriter); | |||
pWriter.print("{"); | |||
if (isXSRFEnabled(application)) { | |||
pWriter.print(getSecurityKeyUIDL(combinedRequest)); | |||
} | |||
writeUidlResponce(null, true, pWriter, root, false); | |||
pWriter.print("}"); | |||
params.put("uidl", sWriter.toString()); | |||
} | |||
response.getWriter().write(params.toString()); | |||
} catch (RootRequiresMoreInformation e) { | |||
// Requiring more information at this point is not allowed |
@@ -7,6 +7,8 @@ package com.vaadin.terminal.gwt.server; | |||
import java.io.BufferedWriter; | |||
import java.io.IOException; | |||
import java.io.OutputStreamWriter; | |||
import java.io.PrintWriter; | |||
import java.io.StringWriter; | |||
import javax.servlet.http.HttpServletResponse; | |||
@@ -22,6 +24,14 @@ import com.vaadin.ui.Root; | |||
public abstract class AjaxPageHandler implements RequestHandler { | |||
/** | |||
* Returns the {@link AbstractCommunicationManager} that is handling the | |||
* communication for the active application. | |||
* | |||
* @return the {@link AbstractCommunicationManager} | |||
*/ | |||
protected abstract AbstractCommunicationManager getCommunicationManager(); | |||
public boolean handleRequest(Application application, | |||
WrappedRequest request, WrappedResponse response) | |||
throws IOException { | |||
@@ -294,6 +304,20 @@ public abstract class AjaxPageHandler implements RequestHandler { | |||
if (application.isRootInitPending(rootId)) { | |||
appConfig.put("initPending", true); | |||
} else { | |||
// write the initial UIDL into the config | |||
AbstractCommunicationManager manager = getCommunicationManager(); | |||
Root root = Root.getCurrentRoot(); | |||
manager.makeAllPaintablesDirty(root); | |||
StringWriter sWriter = new StringWriter(); | |||
PrintWriter pWriter = new PrintWriter(sWriter); | |||
pWriter.print("{"); | |||
if (manager.isXSRFEnabled(application)) { | |||
pWriter.print(manager.getSecurityKeyUIDL(request)); | |||
} | |||
manager.writeUidlResponce(null, true, pWriter, root, false); | |||
pWriter.print("}"); | |||
appConfig.put("uidl", sWriter.toString()); | |||
} | |||
page.write("vaadin.setDefaults("); |