*/
if (json.containsKey("timings")) {
serverTimingInfo = json.getValueMap("timings");
}
Command c = new Command() {
@Override
public void execute() {
assert syncId == -1 || syncId == lastSeenServerSyncId;
handleUIDLDuration.logDuration(" * Loading widgets completed",
10);
Profiler.enter("Handling meta information");
ValueMap meta = null;
if (json.containsKey("meta")) {
VConsole.log(" * Handling meta information");
meta = json.getValueMap("meta");
if (meta.containsKey("repaintAll")) {
prepareRepaintAll();
}
if (meta.containsKey("timedRedirect")) {
final ValueMap timedRedirect = meta
.getValueMap("timedRedirect");
if (redirectTimer != null) {
redirectTimer.cancel();
}
redirectTimer = new Timer() {
@Override
public void run() {
redirect(timedRedirect.getString("url"));
}
};
sessionExpirationInterval = timedRedirect
.getInt("interval");
}
}
Profiler.leave("Handling meta information");
if (redirectTimer != null) {
redirectTimer.schedule(1000 * sessionExpirationInterval);
}
double processUidlStart = Duration.currentTimeMillis();
// Ensure that all connectors that we are about to update exist
JsArrayString createdConnectorIds = createConnectorsIfNeeded(json);
// Update states, do not fire events
JsArrayObject<StateChangeEvent> pendingStateChangeEvents = updateConnectorState(
json, createdConnectorIds);
/*
* Doing this here so that locales are available also to the
* connectors which get a state change event before the UI.
*/
Profiler.enter("Handling locales");
VConsole.log(" * Handling locales");
// Store locale data
LocaleService
.addLocales(getUIConnector().getState().localeServiceState.localeData);
Profiler.leave("Handling locales");
// Update hierarchy, do not fire events
ConnectorHierarchyUpdateResult connectorHierarchyUpdateResult = updateConnectorHierarchy(json);
// Fire hierarchy change events
sendHierarchyChangeEvents(connectorHierarchyUpdateResult.events);
updateCaptions(pendingStateChangeEvents,
connectorHierarchyUpdateResult.parentChangedIds);
delegateToWidget(pendingStateChangeEvents);
// Fire state change events.
sendStateChangeEvents(pendingStateChangeEvents);
// Update of legacy (UIDL) style connectors
updateVaadin6StyleConnectors(json);
// Handle any RPC invocations done on the server side
handleRpcInvocations(json);
if (json.containsKey("dd")) {
// response contains data for drag and drop service
VDragAndDropManager.get().handleServerResponse(
json.getValueMap("dd"));
}
unregisterRemovedConnectors();
VConsole.log("handleUIDLMessage: "
+ (Duration.currentTimeMillis() - processUidlStart)
+ " ms");
Profiler.enter("Layout processing");
try {
LayoutManager layoutManager = getLayoutManager();
layoutManager.setEverythingNeedsMeasure();
layoutManager.layoutNow();
} catch (final Throwable e) {
VConsole.error(e);
}
Profiler.leave("Layout processing");
if (ApplicationConfiguration.isDebugMode()) {
Profiler.enter("Dumping state changes to the console");
VConsole.log(" * Dumping state changes to the console");
VConsole.dirUIDL(json, ApplicationConnection.this);
Profiler.leave("Dumping state changes to the console");
}
if (meta != null) {
Profiler.enter("Error handling");
if (meta.containsKey("appError")) {
ValueMap error = meta.getValueMap("appError");
showError(null, error.getString("caption"),
error.getString("message"),
error.getString("url"));
setApplicationRunning(false);
}
Profiler.leave("Error handling");
}
// TODO build profiling for widget impl loading time
lastProcessingTime = (int) ((new Date().getTime()) - start
.getTime());
totalProcessingTime += lastProcessingTime;
if (bootstrapTime == 0) {
bootstrapTime = calculateBootstrapTime();
if (Profiler.isEnabled() && bootstrapTime != -1) {
Profiler.logBootstrapTimings();
}
}
VConsole.log(" Processing time was "
+ String.valueOf(lastProcessingTime) + "ms for "
+ jsonText.length() + " characters of JSON");
VConsole.log("Referenced paintables: " + connectorMap.size());
if (meta == null || !meta.containsKey("async")) {
// End the request if the received message was a response,
// not sent asynchronously
endRequest();
}
resumeResponseHandling(lock);
if (Profiler.isEnabled()) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
Profiler.logTimings();
Profiler.reset();
}
});
}
}
/**
* Properly clean up any old stuff to ensure everything is properly
* reinitialized.
*/
private void prepareRepaintAll() {
String uiConnectorId = uIConnector.getConnectorId();
if (uiConnectorId == null) {
// Nothing to clear yet
return;
}
// Create fake server response that says that the uiConnector
// has no children
JSONObject fakeHierarchy = new JSONObject();
fakeHierarchy.put(uiConnectorId, new JSONArray());
JSONObject fakeJson = new JSONObject();
fakeJson.put("hierarchy", fakeHierarchy);
ValueMap fakeValueMap = fakeJson.getJavaScriptObject().cast();
// Update hierarchy based on the fake response
ConnectorHierarchyUpdateResult connectorHierarchyUpdateResult = updateConnectorHierarchy(fakeValueMap);
// Send hierarchy events based on the fake update
sendHierarchyChangeEvents(connectorHierarchyUpdateResult.events);
// Unregister all the old connectors that have now been removed
unregisterRemovedConnectors();
getLayoutManager().cleanMeasuredSizes();
}
private void updateCaptions(
JsArrayObject<StateChangeEvent> pendingStateChangeEvents,
FastStringSet parentChangedIds) {
Profiler.enter("updateCaptions");
/*
* Find all components that might need a caption update based on
* pending state and hierarchy changes
*/
FastStringSet needsCaptionUpdate = FastStringSet.create();
needsCaptionUpdate.addAll(parentChangedIds);
// Find components with potentially changed caption state
int size = pendingStateChangeEvents.size();
for (int i = 0; i < size; i++) {
StateChangeEvent event = pendingStateChangeEvents.get(i);
if (VCaption.mightChange(event)) {
ServerConnector connector = event.getConnector();
needsCaptionUpdate.add(connector.getConnectorId());
}
}
ConnectorMap connectorMap = getConnectorMap();
// Update captions for all suitable candidates
JsArrayString dump = needsCaptionUpdate.dump();
int needsUpdateLength = dump.length();
for (int i = 0; i < needsUpdateLength; i++) {
String childId = dump.get(i);
ServerConnector child = connectorMap.getConnector(childId);
if (child instanceof ComponentConnector
&& ((ComponentConnector) child)
.delegateCaptionHandling()) {
ServerConnector parent = child.getParent();
if (parent instanceof HasComponentsConnector) {
Profiler.enter("HasComponentsConnector.updateCaption");
((HasComponentsConnector) parent)
.updateCaption((ComponentConnector) child);
Profiler.leave("HasComponentsConnector.updateCaption");
}
}
}
Profiler.leave("updateCaptions");
}
private void delegateToWidget(
JsArrayObject<StateChangeEvent> pendingStateChangeEvents) {
Profiler.enter("@DelegateToWidget");
VConsole.log(" * Running @DelegateToWidget");
// Keep track of types that have no @DelegateToWidget in their
// state to optimize performance
FastStringSet noOpTypes = FastStringSet.create();
int size = pendingStateChangeEvents.size();
for (int eventIndex = 0; eventIndex < size; eventIndex++) {
StateChangeEvent sce = pendingStateChangeEvents
.get(eventIndex);
ServerConnector connector = sce.getConnector();
if (connector instanceof ComponentConnector) {
String className = connector.getClass().getName();
if (noOpTypes.contains(className)) {
continue;
}
ComponentConnector component = (ComponentConnector) connector;
Type stateType = AbstractConnector
.getStateType(component);
JsArrayString delegateToWidgetProperties = stateType
.getDelegateToWidgetProperties();
if (delegateToWidgetProperties == null) {
noOpTypes.add(className);
continue;
}
int length = delegateToWidgetProperties.length();
for (int i = 0; i < length; i++) {
String propertyName = delegateToWidgetProperties
.get(i);
if (sce.hasPropertyChanged(propertyName)) {
Property property = stateType
.getProperty(propertyName);
String method = property
.getDelegateToWidgetMethodName();
Profiler.enter("doDelegateToWidget");
doDelegateToWidget(component, property, method);
Profiler.leave("doDelegateToWidget");
}
}
}
}
Profiler.leave("@DelegateToWidget");
}
private void doDelegateToWidget(ComponentConnector component,
Property property, String methodName) {
Type type = TypeData.getType(component.getClass());
try {
Type widgetType = type.getMethod("getWidget")
.getReturnType();
Widget widget = component.getWidget();
Object propertyValue = property.getValue(component
.getState());
widgetType.getMethod(methodName).invoke(widget,
propertyValue);
} catch (NoDataException e) {
throw new RuntimeException(
"Missing data needed to invoke @DelegateToWidget for "
+ Util.getSimpleName(component), e);
}
}
/**
* Sends the state change events created while updating the state
* information.
*
* This must be called after hierarchy change listeners have been
* called. At least caption updates for the parent are strange if
* fired from state change listeners and thus calls the parent
* BEFORE the parent is aware of the child (through a
* ConnectorHierarchyChangedEvent)
*
* @param pendingStateChangeEvents
* The events to send
*/
private void sendStateChangeEvents(
JsArrayObject<StateChangeEvent> pendingStateChangeEvents) {
Profiler.enter("sendStateChangeEvents");
VConsole.log(" * Sending state change events");
int size = pendingStateChangeEvents.size();
for (int i = 0; i < size; i++) {
StateChangeEvent sce = pendingStateChangeEvents.get(i);
try {
sce.getConnector().fireEvent(sce);
} catch (final Throwable e) {
VConsole.error(e);
}
}
Profiler.leave("sendStateChangeEvents");
}
private void unregisterRemovedConnectors() {
Profiler.enter("unregisterRemovedConnectors");
int unregistered = 0;
JsArrayObject<ServerConnector> currentConnectors = connectorMap
.getConnectorsAsJsArray();
int size = currentConnectors.size();
for (int i = 0; i < size; i++) {
ServerConnector c = currentConnectors.get(i);
if (c.getParent() != null) {
// only do this check if debug mode is active
if (ApplicationConfiguration.isDebugMode()) {
Profiler.enter("unregisterRemovedConnectors check parent - this is only performed in debug mode");
// this is slow for large layouts, 25-30% of total
// time for some operations even on modern browsers
if (!c.getParent().getChildren().contains(c)) {
VConsole.error("ERROR: Connector is connected to a parent but the parent does not contain the connector");
}
Profiler.leave("unregisterRemovedConnectors check parent - this is only performed in debug mode");
}
} else if (c == getUIConnector()) {