Moved Connector Id -> Connector mapping to Application Moved dirty connector tracking to Root Removed adding oftags/7.0.0.alpha2
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.Hashtable; | import java.util.Hashtable; | ||||
import java.util.Iterator; | |||||
import java.util.LinkedList; | import java.util.LinkedList; | ||||
import java.util.Locale; | import java.util.Locale; | ||||
import java.util.Map; | import java.util.Map; | ||||
import com.vaadin.terminal.WrappedRequest.BrowserDetails; | import com.vaadin.terminal.WrappedRequest.BrowserDetails; | ||||
import com.vaadin.terminal.WrappedResponse; | import com.vaadin.terminal.WrappedResponse; | ||||
import com.vaadin.terminal.gwt.client.ApplicationConnection; | import com.vaadin.terminal.gwt.client.ApplicationConnection; | ||||
import com.vaadin.terminal.gwt.client.Connector; | |||||
import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; | import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; | ||||
import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent; | import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent; | ||||
import com.vaadin.terminal.gwt.server.WebApplicationContext; | import com.vaadin.terminal.gwt.server.WebApplicationContext; | ||||
import com.vaadin.ui.AbstractComponent; | import com.vaadin.ui.AbstractComponent; | ||||
import com.vaadin.ui.AbstractField; | import com.vaadin.ui.AbstractField; | ||||
import com.vaadin.ui.Component; | |||||
import com.vaadin.ui.Root; | import com.vaadin.ui.Root; | ||||
import com.vaadin.ui.Table; | import com.vaadin.ui.Table; | ||||
import com.vaadin.ui.Window; | import com.vaadin.ui.Window; | ||||
public Collection<Root> getRoots() { | public Collection<Root> getRoots() { | ||||
return Collections.unmodifiableCollection(roots.values()); | return Collections.unmodifiableCollection(roots.values()); | ||||
} | } | ||||
private final HashMap<String, Connector> connectorIdToConnector = new HashMap<String, Connector>(); | |||||
private int connectorIdSequence = 0; | |||||
/** | |||||
* Generate an id for the given Connector. Connectors must not call this | |||||
* method more than once, the first time they need an id. | |||||
* | |||||
* @param connector | |||||
* A connector that has not yet been assigned an id. | |||||
* @return A new id for the connector | |||||
*/ | |||||
public String createConnectorId(Connector connector) { | |||||
// String connectorId = "C_" + connectorIdSequence++; | |||||
String connectorId = String.valueOf(connectorIdSequence++); | |||||
Connector oldReference = connectorIdToConnector.put(connectorId, | |||||
connector); | |||||
if (oldReference != null) { | |||||
throw new RuntimeException( | |||||
"An error occured while generating connector ids. A connector with id " | |||||
+ connectorId + " was already found!"); | |||||
} | |||||
return connectorId; | |||||
} | |||||
/** | |||||
* Gets a connector by its id. | |||||
* | |||||
* @param connectorId | |||||
* The connector id to look for | |||||
* @return The connector with the given id or null if no connector has the | |||||
* given id | |||||
*/ | |||||
public Connector getConnector(String connectorId) { | |||||
return connectorIdToConnector.get(connectorId); | |||||
} | |||||
/** | |||||
* Cleans the connector map from all connectors that are no longer attached | |||||
* to the application. This should only be called by the framework. | |||||
*/ | |||||
public void cleanConnectorMap() { | |||||
// remove detached components from paintableIdMap so they | |||||
// can be GC'ed | |||||
Iterator<String> iterator = connectorIdToConnector.keySet().iterator(); | |||||
while (iterator.hasNext()) { | |||||
String connectorId = iterator.next(); | |||||
Connector connector = connectorIdToConnector.get(connectorId); | |||||
if (connector instanceof Component) { | |||||
Component component = (Component) connector; | |||||
if (component.getApplication() != this) { | |||||
// If component is no longer part of this application, | |||||
// remove it from the map. If it is re-attached to the | |||||
// application at some point it will be re-added to this | |||||
// collection when sent to the client. | |||||
iterator.remove(); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | } |
import java.io.Serializable; | import java.io.Serializable; | ||||
import java.util.EventObject; | import java.util.EventObject; | ||||
import java.util.List; | |||||
import com.vaadin.terminal.gwt.client.communication.SharedState; | |||||
import com.vaadin.terminal.gwt.server.ClientMethodInvocation; | |||||
/** | /** | ||||
* Interface implemented by all classes that can be painted. Classes | * Interface implemented by all classes that can be painted. Classes | ||||
*/ | */ | ||||
public void paint(PaintTarget target) throws PaintException; | public void paint(PaintTarget target) throws PaintException; | ||||
/** | |||||
* Returns the current shared state bean for the paintable. The state (or | |||||
* changes to it) is communicated from the server to the client when | |||||
* components are painted. | |||||
* | |||||
* Subclasses can use a more specific return type for this method. | |||||
* | |||||
* @return shared state instance or null | |||||
* | |||||
* @since 7.0 | |||||
*/ | |||||
public SharedState getState(); | |||||
/** | |||||
* Returns the list of pending server to client RPC calls and clears the | |||||
* list. | |||||
* | |||||
* @return unmodifiable ordered list of pending server to client method | |||||
* calls (not null) | |||||
* | |||||
* @since 7.0 | |||||
*/ | |||||
public List<ClientMethodInvocation> retrievePendingRpcCalls(); | |||||
/** | /** | ||||
* Requests that the paintable should be repainted as soon as possible. | * Requests that the paintable should be repainted as soon as possible. | ||||
*/ | */ |
} else if (JsonEncoder.VTYPE_BOOLEAN.equals(variableType)) { | } else if (JsonEncoder.VTYPE_BOOLEAN.equals(variableType)) { | ||||
// TODO handle properly | // TODO handle properly | ||||
val = Boolean.valueOf(String.valueOf(value)); | val = Boolean.valueOf(String.valueOf(value)); | ||||
} else if (JsonEncoder.VTYPE_PAINTABLE.equals(variableType)) { | |||||
} else if (JsonEncoder.VTYPE_CONNECTOR.equals(variableType)) { | |||||
val = idMapper.getConnector(((JSONString) value).stringValue()); | val = idMapper.getConnector(((JSONString) value).stringValue()); | ||||
} else { | } else { | ||||
// object, class name as type | // object, class name as type |
import com.google.gwt.json.client.JSONString; | import com.google.gwt.json.client.JSONString; | ||||
import com.google.gwt.json.client.JSONValue; | import com.google.gwt.json.client.JSONValue; | ||||
import com.vaadin.terminal.gwt.client.ApplicationConnection; | import com.vaadin.terminal.gwt.client.ApplicationConnection; | ||||
import com.vaadin.terminal.gwt.client.Connector; | |||||
import com.vaadin.terminal.gwt.client.ConnectorMap; | import com.vaadin.terminal.gwt.client.ConnectorMap; | ||||
import com.vaadin.terminal.gwt.client.ServerConnector; | |||||
/** | /** | ||||
* Encoder for converting RPC parameters and other values to JSON for transfer | * Encoder for converting RPC parameters and other values to JSON for transfer | ||||
*/ | */ | ||||
public class JsonEncoder { | public class JsonEncoder { | ||||
public static final String VTYPE_PAINTABLE = "p"; | |||||
public static final String VTYPE_CONNECTOR = "c"; | |||||
public static final String VTYPE_BOOLEAN = "b"; | public static final String VTYPE_BOOLEAN = "b"; | ||||
public static final String VTYPE_DOUBLE = "d"; | public static final String VTYPE_DOUBLE = "d"; | ||||
public static final String VTYPE_FLOAT = "f"; | public static final String VTYPE_FLOAT = "f"; | ||||
public static final String VTYPE_INTEGER = "i"; | public static final String VTYPE_INTEGER = "i"; | ||||
public static final String VTYPE_STRING = "s"; | public static final String VTYPE_STRING = "s"; | ||||
public static final String VTYPE_ARRAY = "a"; | public static final String VTYPE_ARRAY = "a"; | ||||
public static final String VTYPE_STRINGARRAY = "c"; | |||||
public static final String VTYPE_STRINGARRAY = "S"; | |||||
public static final String VTYPE_MAP = "m"; | public static final String VTYPE_MAP = "m"; | ||||
public static final String VTYPE_LIST = "L"; | public static final String VTYPE_LIST = "L"; | ||||
public static final String VTYPE_NULL = "n"; | public static final String VTYPE_NULL = "n"; | ||||
jsonMap.put(mapKey, encode(mapValue, connectorMap, connection)); | jsonMap.put(mapKey, encode(mapValue, connectorMap, connection)); | ||||
} | } | ||||
return combineTypeAndValue(VTYPE_MAP, jsonMap); | return combineTypeAndValue(VTYPE_MAP, jsonMap); | ||||
} else if (value instanceof ServerConnector) { | |||||
ServerConnector paintable = (ServerConnector) value; | |||||
return combineTypeAndValue(VTYPE_PAINTABLE, new JSONString( | |||||
connectorMap.getConnectorId(paintable))); | |||||
} else if (value instanceof Connector) { | |||||
Connector connector = (Connector) value; | |||||
return combineTypeAndValue(VTYPE_CONNECTOR, new JSONString( | |||||
connector.getConnectorId())); | |||||
} else { | } else { | ||||
String transportType = getTransportType(value); | String transportType = getTransportType(value); | ||||
if (transportType != null) { | if (transportType != null) { | ||||
return VTYPE_NULL; | return VTYPE_NULL; | ||||
} else if (value instanceof String) { | } else if (value instanceof String) { | ||||
return VTYPE_STRING; | return VTYPE_STRING; | ||||
} else if (value instanceof ServerConnector) { | |||||
return VTYPE_PAINTABLE; | |||||
} else if (value instanceof Connector) { | |||||
return VTYPE_CONNECTOR; | |||||
} else if (value instanceof Boolean) { | } else if (value instanceof Boolean) { | ||||
return VTYPE_BOOLEAN; | return VTYPE_BOOLEAN; | ||||
} else if (value instanceof Integer) { | } else if (value instanceof Integer) { |
ENTER, LEAVE, OVER, DROP | ENTER, LEAVE, OVER, DROP | ||||
} | } | ||||
private static final String DD_SERVICE = "DD"; | |||||
public static final String DD_SERVICE = "DD"; | |||||
private static VDragAndDropManager instance; | private static VDragAndDropManager instance; | ||||
private HandlerRegistration handlerRegistration; | private HandlerRegistration handlerRegistration; |
/* Handle the request */ | /* Handle the request */ | ||||
if (requestType == RequestType.FILE_UPLOAD) { | if (requestType == RequestType.FILE_UPLOAD) { | ||||
applicationManager.handleFileUpload(request, response); | |||||
applicationManager.handleFileUpload(application, request, | |||||
response); | |||||
return; | return; | ||||
} else if (requestType == RequestType.UIDL) { | } else if (requestType == RequestType.UIDL) { | ||||
// Handles AJAX UIDL requests | // Handles AJAX UIDL requests |
import com.vaadin.ui.AbstractComponent; | import com.vaadin.ui.AbstractComponent; | ||||
import com.vaadin.ui.AbstractField; | import com.vaadin.ui.AbstractField; | ||||
import com.vaadin.ui.Component; | import com.vaadin.ui.Component; | ||||
import com.vaadin.ui.DirtyConnectorTracker; | |||||
import com.vaadin.ui.HasComponents; | import com.vaadin.ui.HasComponents; | ||||
import com.vaadin.ui.Panel; | import com.vaadin.ui.Panel; | ||||
import com.vaadin.ui.Root; | import com.vaadin.ui.Root; | ||||
import com.vaadin.ui.Window; | |||||
/** | /** | ||||
* This is a common base class for the server-side implementations of the | * This is a common base class for the server-side implementations of the | ||||
*/ | */ | ||||
@SuppressWarnings("serial") | @SuppressWarnings("serial") | ||||
public abstract class AbstractCommunicationManager implements | public abstract class AbstractCommunicationManager implements | ||||
Paintable.RepaintRequestListener, PaintableIdMapper, Serializable { | |||||
Paintable.RepaintRequestListener, Serializable { | |||||
private static final String DASHDASH = "--"; | private static final String DASHDASH = "--"; | ||||
// cannot combine with paint queue: | // cannot combine with paint queue: | ||||
// this can contain dirty components from any Root | // this can contain dirty components from any Root | ||||
private final ArrayList<Paintable> dirtyPaintables = new ArrayList<Paintable>(); | |||||
// TODO Remove, it is now in Root | |||||
// private final ArrayList<Paintable> dirtyPaintables = new | |||||
// ArrayList<Paintable>(); | |||||
// queue used during painting to keep track of what still needs to be | // queue used during painting to keep track of what still needs to be | ||||
// painted within the Root being painted | // painted within the Root being painted | ||||
private LinkedList<Paintable> paintQueue = new LinkedList<Paintable>(); | |||||
// private LinkedList<Connector> paintQueue = new LinkedList<Connector>(); | |||||
private final HashMap<Paintable, String> paintableIdMap = new HashMap<Paintable, String>(); | |||||
private final HashMap<String, Paintable> idPaintableMap = new HashMap<String, Paintable>(); | |||||
// private final HashMap<String, Paintable> idPaintableMap = new | |||||
// HashMap<String, Paintable>(); | |||||
private int idSequence = 0; | private int idSequence = 0; | ||||
private int maxInactiveInterval; | private int maxInactiveInterval; | ||||
private Connector highlightedConnector; | |||||
/** | /** | ||||
* TODO New constructor - document me! | * TODO New constructor - document me! | ||||
* | * | ||||
private static final String GET_PARAM_HIGHLIGHT_COMPONENT = "highlightComponent"; | private static final String GET_PARAM_HIGHLIGHT_COMPONENT = "highlightComponent"; | ||||
private Paintable highLightedPaintable; | |||||
private static String readLine(InputStream stream) throws IOException { | private static String readLine(InputStream stream) throws IOException { | ||||
ByteArrayOutputStream bout = new ByteArrayOutputStream(); | ByteArrayOutputStream bout = new ByteArrayOutputStream(); | ||||
int readByte = stream.read(); | int readByte = stream.read(); | ||||
*/ | */ | ||||
protected void doHandleSimpleMultipartFileUpload(WrappedRequest request, | protected void doHandleSimpleMultipartFileUpload(WrappedRequest request, | ||||
WrappedResponse response, StreamVariable streamVariable, | WrappedResponse response, StreamVariable streamVariable, | ||||
String variableName, VariableOwner owner, String boundary) | |||||
String variableName, Connector owner, String boundary) | |||||
throws IOException { | throws IOException { | ||||
// multipart parsing, supports only one file for request, but that is | // multipart parsing, supports only one file for request, but that is | ||||
// fine for our current terminal | // fine for our current terminal | ||||
*/ | */ | ||||
protected void doHandleXhrFilePost(WrappedRequest request, | protected void doHandleXhrFilePost(WrappedRequest request, | ||||
WrappedResponse response, StreamVariable streamVariable, | WrappedResponse response, StreamVariable streamVariable, | ||||
String variableName, VariableOwner owner, int contentLength) | |||||
String variableName, Connector owner, int contentLength) | |||||
throws IOException { | throws IOException { | ||||
// These are unknown in filexhr ATM, maybe add to Accept header that | // These are unknown in filexhr ATM, maybe add to Accept header that | ||||
if (request.getParameter(GET_PARAM_HIGHLIGHT_COMPONENT) != null) { | if (request.getParameter(GET_PARAM_HIGHLIGHT_COMPONENT) != null) { | ||||
String pid = request | String pid = request | ||||
.getParameter(GET_PARAM_HIGHLIGHT_COMPONENT); | .getParameter(GET_PARAM_HIGHLIGHT_COMPONENT); | ||||
highLightedPaintable = idPaintableMap.get(pid); | |||||
highlightPaintable(highLightedPaintable); | |||||
highlightedConnector = root.getApplication().getConnector(pid); | |||||
highlightConnector(highlightedConnector); | |||||
} | } | ||||
} | } | ||||
paintAfterVariableChanges(request, response, callback, repaintAll, | paintAfterVariableChanges(request, response, callback, repaintAll, | ||||
outWriter, root, analyzeLayouts); | outWriter, root, analyzeLayouts); | ||||
postPaint(root); | |||||
} | } | ||||
outWriter.close(); | outWriter.close(); | ||||
requestThemeName = null; | requestThemeName = null; | ||||
} | } | ||||
protected void highlightPaintable(Paintable highLightedPaintable2) { | |||||
/** | |||||
* Method called after the paint phase while still being synchronized on the | |||||
* application | |||||
* | |||||
* @param root | |||||
* | |||||
*/ | |||||
protected void postPaint(Root root) { | |||||
// Remove connectors that have been detached from the application during | |||||
// handling of the request | |||||
root.getApplication().cleanConnectorMap(); | |||||
} | |||||
protected void highlightConnector(Connector highlightedConnector) { | |||||
StringBuilder sb = new StringBuilder(); | StringBuilder sb = new StringBuilder(); | ||||
sb.append("*** Debug details of a component: *** \n"); | sb.append("*** Debug details of a component: *** \n"); | ||||
sb.append("Type: "); | sb.append("Type: "); | ||||
sb.append(highLightedPaintable2.getClass().getName()); | |||||
if (highLightedPaintable2 instanceof AbstractComponent) { | |||||
AbstractComponent component = (AbstractComponent) highLightedPaintable2; | |||||
sb.append(highlightedConnector.getClass().getName()); | |||||
if (highlightedConnector instanceof AbstractComponent) { | |||||
AbstractComponent component = (AbstractComponent) highlightedConnector; | |||||
sb.append("\nId:"); | sb.append("\nId:"); | ||||
sb.append(paintableIdMap.get(component)); | |||||
sb.append(highlightedConnector.getConnectorId()); | |||||
if (component.getCaption() != null) { | if (component.getCaption() != null) { | ||||
sb.append("\nCaption:"); | sb.append("\nCaption:"); | ||||
sb.append(component.getCaption()); | sb.append(component.getCaption()); | ||||
final PrintWriter outWriter, Root root, boolean analyzeLayouts) | final PrintWriter outWriter, Root root, boolean analyzeLayouts) | ||||
throws PaintException, IOException { | throws PaintException, IOException { | ||||
if (repaintAll) { | |||||
makeAllPaintablesDirty(root); | |||||
} | |||||
// Removes application if it has stopped during variable changes | // Removes application if it has stopped during variable changes | ||||
if (!application.isRunning()) { | if (!application.isRunning()) { | ||||
endApplication(request, response, application); | endApplication(request, response, application); | ||||
// for internal use by JsonPaintTarget | // for internal use by JsonPaintTarget | ||||
public void queuePaintable(Paintable paintable) { | public void queuePaintable(Paintable paintable) { | ||||
if (!paintQueue.contains(paintable)) { | |||||
paintQueue.add(paintable); | |||||
} | |||||
// if (!paintQueue.contains(paintable)) { | |||||
// paintQueue.add((Connector) paintable); | |||||
// } | |||||
} | } | ||||
public void writeUidlResponse(boolean repaintAll, | public void writeUidlResponse(boolean repaintAll, | ||||
final PrintWriter outWriter, Root root, boolean analyzeLayouts) | final PrintWriter outWriter, Root root, boolean analyzeLayouts) | ||||
throws PaintException { | throws PaintException { | ||||
ArrayList<Paintable> paintables = null; | |||||
ArrayList<Connector> dirtyVisibleConnectors = new ArrayList<Connector>(); | |||||
Application application = root.getApplication(); | |||||
// Paints components | // Paints components | ||||
DirtyConnectorTracker rootConnectorTracker = root | |||||
.getDirtyConnectorTracker(); | |||||
System.out.println("* Creating response to client"); | |||||
if (repaintAll) { | if (repaintAll) { | ||||
paintables = new ArrayList<Paintable>(); | |||||
paintables.add(root); | |||||
System.out.println("Full repaint"); | |||||
getClientCache(root).clear(); | |||||
rootConnectorTracker.markAllComponentsDirty(); | |||||
dirtyVisibleConnectors.addAll(rootConnectorTracker | |||||
.getDirtyComponents()); | |||||
// Reset sent locales | // Reset sent locales | ||||
locales = null; | locales = null; | ||||
requireLocale(application.getLocale().toString()); | requireLocale(application.getLocale().toString()); | ||||
} else { | } else { | ||||
// remove detached components from paintableIdMap so they | |||||
// can be GC'ed | |||||
/* | |||||
* TODO figure out if we could move this beyond the painting phase, | |||||
* "respond as fast as possible, then do the cleanup". Beware of | |||||
* painting the dirty detached components. | |||||
*/ | |||||
for (Iterator<Paintable> it = paintableIdMap.keySet().iterator(); it | |||||
.hasNext();) { | |||||
Component p = (Component) it.next(); | |||||
if (p.getApplication() == null) { | |||||
unregisterPaintable(p); | |||||
// Take into account that some other component may have | |||||
// reused p's ID by now (this can happen when manually | |||||
// assigning IDs with setDebugId().) See #8090. | |||||
String pid = paintableIdMap.get(p); | |||||
if (idPaintableMap.get(pid) == p) { | |||||
idPaintableMap.remove(pid); | |||||
} | |||||
it.remove(); | |||||
dirtyPaintables.remove(p); | |||||
} | |||||
} | |||||
// TODO second list/stack for those whose state needs to be sent? | // TODO second list/stack for those whose state needs to be sent? | ||||
paintables = getDirtyVisibleComponents(root); | |||||
dirtyVisibleConnectors | |||||
.addAll(getDirtyVisibleComponents(rootConnectorTracker)); | |||||
} | } | ||||
System.out.println("* Found " + dirtyVisibleConnectors.size() | |||||
+ " dirty connectors to paint"); | |||||
rootConnectorTracker.markAllComponentsClean(); | |||||
outWriter.print("\"changes\":["); | outWriter.print("\"changes\":["); | ||||
List<InvalidLayout> invalidComponentRelativeSizes = null; | List<InvalidLayout> invalidComponentRelativeSizes = null; | ||||
JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter, | JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter, | ||||
!repaintAll); | !repaintAll); | ||||
ClientCache clientCache = getClientCache(root); | |||||
// TODO These seem unnecessary and could be removed/replaced by looping | |||||
// through paintQueue without removing paintables from it | |||||
LinkedList<Paintable> stateQueue = new LinkedList<Paintable>(); | |||||
LinkedList<Paintable> rpcPendingQueue = new LinkedList<Paintable>(); | |||||
LinkedList<Paintable> hierarchyPendingQueue = new LinkedList<Paintable>(); | |||||
LinkedList<Paintable> connectorTypeQueue = new LinkedList<Paintable>(); | |||||
if (paintables != null) { | |||||
// clear and rebuild paint queue | |||||
paintQueue.clear(); | |||||
paintQueue.addAll(paintables); | |||||
while (!paintQueue.isEmpty()) { | |||||
final Paintable p = paintQueue.removeFirst(); | |||||
// for now, all painted components may need a state refresh | |||||
stateQueue.push(p); | |||||
// TODO This should be optimized. The type only needs to be sent | |||||
// once for each connector id + on refresh | |||||
connectorTypeQueue.push(p); | |||||
// also a hierarchy update | |||||
hierarchyPendingQueue.push(p); | |||||
// ... and RPC calls to be sent | |||||
rpcPendingQueue.push(p); | |||||
// // TODO CLEAN | |||||
// if (p instanceof Root) { | |||||
// final Root r = (Root) p; | |||||
// if (r.getTerminal() == null) { | |||||
// r.setTerminal(application.getRoot().getTerminal()); | |||||
// } | |||||
// } | |||||
// TODO we may still get changes that have been | |||||
// rendered already (changes with only cached flag) | |||||
if (paintTarget.needsToBePainted(p)) { | |||||
paintTarget.startTag("change"); | |||||
final String pid = getPaintableId(p); | |||||
paintTarget.addAttribute("pid", pid); | |||||
// paints subcomponents as references (via | |||||
// JsonPaintTarget.startPaintable()) and defers painting | |||||
// their contents to another top-level change (via | |||||
// queuePaintable()) | |||||
p.paint(paintTarget); | |||||
paintTarget.endTag("change"); | |||||
} | |||||
paintablePainted(p); | |||||
for (Connector connector : dirtyVisibleConnectors) { | |||||
if (connector instanceof Paintable) { | |||||
Paintable p = (Paintable) connector; | |||||
paintTarget.startTag("change"); | |||||
final String pid = connector.getConnectorId(); | |||||
paintTarget.addAttribute("pid", pid); | |||||
p.paint(paintTarget); | |||||
paintTarget.endTag("change"); | |||||
} | |||||
} | |||||
if (analyzeLayouts) { | |||||
Root w = (Root) p; | |||||
if (analyzeLayouts) { | |||||
// TODO Check if this works | |||||
invalidComponentRelativeSizes = ComponentSizeValidator | |||||
.validateComponentRelativeSizes(root.getContent(), null, | |||||
null); | |||||
// Also check any existing subwindows | |||||
if (root.getWindows() != null) { | |||||
for (Window subWindow : root.getWindows()) { | |||||
invalidComponentRelativeSizes = ComponentSizeValidator | invalidComponentRelativeSizes = ComponentSizeValidator | ||||
.validateComponentRelativeSizes(w.getContent(), | |||||
null, null); | |||||
// // Also check any existing subwindows | |||||
// if (w.getChildWindows() != null) { | |||||
// for (Window subWindow : w.getChildWindows()) { | |||||
// invalidComponentRelativeSizes = ComponentSizeValidator | |||||
// .validateComponentRelativeSizes( | |||||
// subWindow.getContent(), | |||||
// invalidComponentRelativeSizes, null); | |||||
// } | |||||
// } | |||||
.validateComponentRelativeSizes( | |||||
subWindow.getContent(), | |||||
invalidComponentRelativeSizes, null); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
paintTarget.close(); | paintTarget.close(); | ||||
outWriter.print("], "); // close changes | outWriter.print("], "); // close changes | ||||
if (!stateQueue.isEmpty()) { | |||||
// send shared state to client | |||||
// for now, send the complete state of all modified and new | |||||
// components | |||||
// Ideally, all this would be sent before "changes", but that causes | |||||
// complications with legacy components that create sub-components | |||||
// in their paint phase. Nevertheless, this will be processed on the | |||||
// client after component creation but before legacy UIDL | |||||
// processing. | |||||
JSONObject sharedStates = new JSONObject(); | |||||
while (!stateQueue.isEmpty()) { | |||||
final Paintable p = stateQueue.pop(); | |||||
String paintableId = getPaintableId(p); | |||||
SharedState state = p.getState(); | |||||
if (null != state) { | |||||
// encode and send shared state | |||||
try { | |||||
JSONArray stateJsonArray = JsonCodec | |||||
.encode(state, this); | |||||
sharedStates.put(paintableId, stateJsonArray); | |||||
} catch (JSONException e) { | |||||
throw new PaintException( | |||||
"Failed to serialize shared state for paintable " | |||||
+ paintableId + ": " + e.getMessage()); | |||||
} | |||||
} | |||||
} | |||||
outWriter.print("\"state\":"); | |||||
outWriter.append(sharedStates.toString()); | |||||
outWriter.print(", "); // close states | |||||
} | |||||
if (!connectorTypeQueue.isEmpty()) { | |||||
JSONObject connectorTypes = new JSONObject(); | |||||
while (!connectorTypeQueue.isEmpty()) { | |||||
final Paintable p = connectorTypeQueue.pop(); | |||||
String paintableId = getPaintableId(p); | |||||
String connectorType = paintTarget.getTag(p); | |||||
// send shared state to client | |||||
// for now, send the complete state of all modified and new | |||||
// components | |||||
// Ideally, all this would be sent before "changes", but that causes | |||||
// complications with legacy components that create sub-components | |||||
// in their paint phase. Nevertheless, this will be processed on the | |||||
// client after component creation but before legacy UIDL | |||||
// processing. | |||||
JSONObject sharedStates = new JSONObject(); | |||||
for (Connector connector : dirtyVisibleConnectors) { | |||||
SharedState state = connector.getState(); | |||||
if (null != state) { | |||||
// encode and send shared state | |||||
try { | try { | ||||
connectorTypes.put(paintableId, connectorType); | |||||
JSONArray stateJsonArray = JsonCodec.encode(state, | |||||
application); | |||||
sharedStates | |||||
.put(connector.getConnectorId(), stateJsonArray); | |||||
} catch (JSONException e) { | } catch (JSONException e) { | ||||
throw new PaintException( | throw new PaintException( | ||||
"Failed to send connector type for paintable " | |||||
+ paintableId + ": " + e.getMessage()); | |||||
} | |||||
} | |||||
outWriter.print("\"types\":"); | |||||
outWriter.append(connectorTypes.toString()); | |||||
outWriter.print(", "); // close states | |||||
} | |||||
if (!hierarchyPendingQueue.isEmpty()) { | |||||
// Send update hierarchy information to the client. | |||||
// This could be optimized aswell to send only info if hierarchy has | |||||
// actually changed. Much like with the shared state. Note though | |||||
// that an empty hierarchy is information aswell (e.g. change from 1 | |||||
// child to 0 children) | |||||
outWriter.print("\"hierarchy\":"); | |||||
JSONObject hierarchyInfo = new JSONObject(); | |||||
for (Paintable p : hierarchyPendingQueue) { | |||||
if (p instanceof HasComponents) { | |||||
HasComponents parent = (HasComponents) p; | |||||
String parentConnectorId = paintableIdMap.get(parent); | |||||
JSONArray children = new JSONArray(); | |||||
for (Component child : getChildComponents(parent)) { | |||||
if (isVisible(child)) { | |||||
String childConnectorId = getPaintableId(child); | |||||
children.put(childConnectorId); | |||||
} | |||||
} | |||||
try { | |||||
hierarchyInfo.put(parentConnectorId, children); | |||||
} catch (JSONException e) { | |||||
throw new PaintException( | |||||
"Failed to send hierarchy information about " | |||||
+ parentConnectorId | |||||
+ " to the client: " + e.getMessage()); | |||||
} | |||||
"Failed to serialize shared state for connector " | |||||
+ connector.getConnectorId() + ": " | |||||
+ e.getMessage()); | |||||
} | } | ||||
} | } | ||||
outWriter.append(hierarchyInfo.toString()); | |||||
outWriter.print(", "); // close hierarchy | |||||
} | |||||
outWriter.print("\"state\":"); | |||||
outWriter.append(sharedStates.toString()); | |||||
outWriter.print(", "); // close states | |||||
// TODO This should be optimized. The type only needs to be | |||||
// sent once for each connector id + on refresh | |||||
JSONObject connectorTypes = new JSONObject(); | |||||
for (Connector connector : dirtyVisibleConnectors) { | |||||
String connectorType = paintTarget.getTag((Paintable) connector); | |||||
try { | |||||
connectorTypes.put(connector.getConnectorId(), connectorType); | |||||
} catch (JSONException e) { | |||||
throw new PaintException( | |||||
"Failed to send connector type for connector " | |||||
+ connector.getConnectorId() + ": " | |||||
+ e.getMessage()); | |||||
} | |||||
} | } | ||||
outWriter.print("\"types\":"); | |||||
outWriter.append(connectorTypes.toString()); | |||||
outWriter.print(", "); // close states | |||||
// send server to client RPC calls for components in the root, in call | |||||
// order | |||||
if (!rpcPendingQueue.isEmpty()) { | |||||
// collect RPC calls from components in the root in the order in | |||||
// which they were performed, remove the calls from components | |||||
List<ClientMethodInvocation> pendingInvocations = collectPendingRpcCalls(rpcPendingQueue); | |||||
JSONArray rpcCalls = new JSONArray(); | |||||
for (ClientMethodInvocation invocation : pendingInvocations) { | |||||
// add invocation to rpcCalls | |||||
try { | |||||
JSONArray invocationJson = new JSONArray(); | |||||
invocationJson | |||||
.put(getPaintableId(invocation.getPaintable())); | |||||
invocationJson.put(invocation.getInterfaceName()); | |||||
invocationJson.put(invocation.getMethodName()); | |||||
JSONArray paramJson = new JSONArray(); | |||||
for (int i = 0; i < invocation.getParameters().length; ++i) { | |||||
paramJson.put(JsonCodec.encode( | |||||
invocation.getParameters()[i], this)); | |||||
// Send update hierarchy information to the client. | |||||
// This could be optimized aswell to send only info if hierarchy has | |||||
// actually changed. Much like with the shared state. Note though | |||||
// that an empty hierarchy is information aswell (e.g. change from 1 | |||||
// child to 0 children) | |||||
outWriter.print("\"hierarchy\":"); | |||||
JSONObject hierarchyInfo = new JSONObject(); | |||||
for (Connector connector : dirtyVisibleConnectors) { | |||||
if (connector instanceof HasComponents) { | |||||
HasComponents parent = (HasComponents) connector; | |||||
String parentConnectorId = parent.getConnectorId(); | |||||
JSONArray children = new JSONArray(); | |||||
for (Component child : getChildComponents(parent)) { | |||||
if (isVisible(child)) { | |||||
children.put(child.getConnectorId()); | |||||
} | } | ||||
invocationJson.put(paramJson); | |||||
rpcCalls.put(invocationJson); | |||||
} | |||||
try { | |||||
hierarchyInfo.put(parentConnectorId, children); | |||||
} catch (JSONException e) { | } catch (JSONException e) { | ||||
throw new PaintException( | throw new PaintException( | ||||
"Failed to serialize RPC method call parameters for paintable " | |||||
+ getPaintableId(invocation.getPaintable()) | |||||
+ " method " | |||||
+ invocation.getInterfaceName() + "." | |||||
+ invocation.getMethodName() + ": " | |||||
"Failed to send hierarchy information about " | |||||
+ parentConnectorId + " to the client: " | |||||
+ e.getMessage()); | + e.getMessage()); | ||||
} | } | ||||
} | } | ||||
} | |||||
outWriter.append(hierarchyInfo.toString()); | |||||
outWriter.print(", "); // close hierarchy | |||||
// send server to client RPC calls for components in the root, in call | |||||
// order | |||||
// collect RPC calls from components in the root in the order in | |||||
// which they were performed, remove the calls from components | |||||
LinkedList<ClientConnector> rpcPendingQueue = new LinkedList<ClientConnector>( | |||||
(Collection<? extends ClientConnector>) dirtyVisibleConnectors); | |||||
List<ClientMethodInvocation> pendingInvocations = collectPendingRpcCalls(rpcPendingQueue); | |||||
if (rpcCalls.length() > 0) { | |||||
outWriter.print("\"rpc\" : "); | |||||
outWriter.append(rpcCalls.toString()); | |||||
outWriter.print(", "); // close rpc | |||||
JSONArray rpcCalls = new JSONArray(); | |||||
for (ClientMethodInvocation invocation : pendingInvocations) { | |||||
// add invocation to rpcCalls | |||||
try { | |||||
JSONArray invocationJson = new JSONArray(); | |||||
invocationJson.put(invocation.getConnector().getConnectorId()); | |||||
invocationJson.put(invocation.getInterfaceName()); | |||||
invocationJson.put(invocation.getMethodName()); | |||||
JSONArray paramJson = new JSONArray(); | |||||
for (int i = 0; i < invocation.getParameters().length; ++i) { | |||||
paramJson.put(JsonCodec.encode( | |||||
invocation.getParameters()[i], application)); | |||||
} | |||||
invocationJson.put(paramJson); | |||||
rpcCalls.put(invocationJson); | |||||
} catch (JSONException e) { | |||||
throw new PaintException( | |||||
"Failed to serialize RPC method call parameters for connector " | |||||
+ invocation.getConnector().getConnectorId() | |||||
+ " method " + invocation.getInterfaceName() | |||||
+ "." + invocation.getMethodName() + ": " | |||||
+ e.getMessage()); | |||||
} | } | ||||
} | |||||
if (rpcCalls.length() > 0) { | |||||
outWriter.print("\"rpc\" : "); | |||||
outWriter.append(rpcCalls.toString()); | |||||
outWriter.print(", "); // close rpc | |||||
} | } | ||||
outWriter.print("\"meta\" : {"); | outWriter.print("\"meta\" : {"); | ||||
} | } | ||||
outWriter.write("]"); | outWriter.write("]"); | ||||
} | } | ||||
if (highLightedPaintable != null) { | |||||
if (highlightedConnector != null) { | |||||
outWriter.write(", \"hl\":\""); | outWriter.write(", \"hl\":\""); | ||||
outWriter.write(paintableIdMap.get(highLightedPaintable)); | |||||
outWriter.write(highlightedConnector.getConnectorId()); | |||||
outWriter.write("\""); | outWriter.write("\""); | ||||
highLightedPaintable = null; | |||||
highlightedConnector = null; | |||||
} | } | ||||
} | } | ||||
Collection<Class<? extends Paintable>> usedPaintableTypes = paintTarget | Collection<Class<? extends Paintable>> usedPaintableTypes = paintTarget | ||||
.getUsedPaintableTypes(); | .getUsedPaintableTypes(); | ||||
boolean typeMappingsOpen = false; | boolean typeMappingsOpen = false; | ||||
ClientCache clientCache = getClientCache(root); | |||||
for (Class<? extends Paintable> class1 : usedPaintableTypes) { | for (Class<? extends Paintable> class1 : usedPaintableTypes) { | ||||
if (clientCache.cache(class1)) { | if (clientCache.cache(class1)) { | ||||
// client does not know the mapping key for this type, send | // client does not know the mapping key for this type, send | ||||
* @return ordered list of pending RPC calls | * @return ordered list of pending RPC calls | ||||
*/ | */ | ||||
private List<ClientMethodInvocation> collectPendingRpcCalls( | private List<ClientMethodInvocation> collectPendingRpcCalls( | ||||
LinkedList<Paintable> rpcPendingQueue) { | |||||
LinkedList<ClientConnector> rpcPendingQueue) { | |||||
List<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>(); | List<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>(); | ||||
for (Paintable paintable : rpcPendingQueue) { | |||||
List<ClientMethodInvocation> paintablePendingRpc = paintable | |||||
for (ClientConnector connector : rpcPendingQueue) { | |||||
List<ClientMethodInvocation> paintablePendingRpc = connector | |||||
.retrievePendingRpcCalls(); | .retrievePendingRpcCalls(); | ||||
if (null != paintablePendingRpc && !paintablePendingRpc.isEmpty()) { | if (null != paintablePendingRpc && !paintablePendingRpc.isEmpty()) { | ||||
List<ClientMethodInvocation> oldPendingRpc = pendingInvocations; | List<ClientMethodInvocation> oldPendingRpc = pendingInvocations; | ||||
return requestThemeName; | return requestThemeName; | ||||
} | } | ||||
public void makeAllPaintablesDirty(Root root) { | |||||
// If repaint is requested, clean all ids in this root window | |||||
for (final Iterator<String> it = idPaintableMap.keySet().iterator(); it | |||||
.hasNext();) { | |||||
final Component c = (Component) idPaintableMap.get(it.next()); | |||||
if (isChildOf(root, c)) { | |||||
it.remove(); | |||||
paintableIdMap.remove(c); | |||||
} | |||||
} | |||||
// clean WindowCache | |||||
getClientCache(root).clear(); | |||||
} | |||||
/** | |||||
* Called when communication manager stops listening for repaints for given | |||||
* component. | |||||
* | |||||
* @param p | |||||
*/ | |||||
protected void unregisterPaintable(Component p) { | |||||
p.removeListener(this); | |||||
} | |||||
/** | /** | ||||
* Returns false if the cross site request forgery protection is turned off. | * Returns false if the cross site request forgery protection is turned off. | ||||
* | * | ||||
if (!ApplicationConnection.UPDATE_VARIABLE_INTERFACE | if (!ApplicationConnection.UPDATE_VARIABLE_INTERFACE | ||||
.equals(interfaceName)) { | .equals(interfaceName)) { | ||||
// handle other RPC calls than variable changes | // handle other RPC calls than variable changes | ||||
applyInvocation(invocation); | |||||
applyInvocation(app, invocation); | |||||
continue; | continue; | ||||
} | } | ||||
final VariableOwner owner = getVariableOwner(invocation | |||||
.getConnectorId()); | |||||
final VariableOwner owner = (VariableOwner) getConnector(app, | |||||
invocation.getConnectorId()); | |||||
boolean connectorEnabled; | |||||
if (owner instanceof Connector) { | |||||
connectorEnabled = ((Connector) owner).isConnectorEnabled(); | |||||
} else { | |||||
// TODO Remove | |||||
connectorEnabled = owner.isEnabled(); | |||||
} | |||||
boolean connectorEnabled = owner.isEnabled(); | |||||
if (owner != null && connectorEnabled) { | if (owner != null && connectorEnabled) { | ||||
VariableChange change = new VariableChange(invocation); | VariableChange change = new VariableChange(invocation); | ||||
* Execute an RPC call from the client by finding its target and letting the | * Execute an RPC call from the client by finding its target and letting the | ||||
* RPC mechanism call the correct method for it. | * RPC mechanism call the correct method for it. | ||||
* | * | ||||
* @param app | |||||
* | |||||
* @param invocation | * @param invocation | ||||
*/ | */ | ||||
protected void applyInvocation(MethodInvocation invocation) { | |||||
final RpcTarget target = getRpcTarget(invocation.getConnectorId()); | |||||
if (null != target) { | |||||
ServerRpcManager.applyInvocation(target, invocation); | |||||
protected void applyInvocation(Application app, MethodInvocation invocation) { | |||||
Connector c = app.getConnector(invocation.getConnectorId()); | |||||
if (c instanceof RpcTarget) { | |||||
ServerRpcManager.applyInvocation((RpcTarget) c, invocation); | |||||
} else { | } else { | ||||
// TODO better exception? | // TODO better exception? | ||||
throw new RuntimeException("No RPC target for paintable " | |||||
throw new RuntimeException("No RPC target for connector " | |||||
+ invocation.getConnectorId()); | + invocation.getConnectorId()); | ||||
} | } | ||||
} | } | ||||
Object[] parameters = new Object[parametersJson.length()]; | Object[] parameters = new Object[parametersJson.length()]; | ||||
for (int j = 0; j < parametersJson.length(); ++j) { | for (int j = 0; j < parametersJson.length(); ++j) { | ||||
parameters[j] = JsonCodec.decode( | parameters[j] = JsonCodec.decode( | ||||
parametersJson.getJSONArray(j), this); | |||||
parametersJson.getJSONArray(j), application); | |||||
} | } | ||||
MethodInvocation invocation = new MethodInvocation(connectorId, | MethodInvocation invocation = new MethodInvocation(connectorId, | ||||
interfaceName, methodName, parameters); | interfaceName, methodName, parameters); | ||||
owner.changeVariables(source, m); | owner.changeVariables(source, m); | ||||
} | } | ||||
protected VariableOwner getVariableOwner(String string) { | |||||
VariableOwner owner = (VariableOwner) idPaintableMap.get(string); | |||||
if (owner == null && string.startsWith("DD")) { | |||||
protected Connector getConnector(Application app, String connectorId) { | |||||
Connector c = app.getConnector(connectorId); | |||||
if (c == null | |||||
&& connectorId.equals(getDragAndDropService().getConnectorId())) { | |||||
return getDragAndDropService(); | return getDragAndDropService(); | ||||
} | } | ||||
return owner; | |||||
} | |||||
/** | |||||
* Returns the RPC call target for a paintable ID. | |||||
* | |||||
* @since 7.0 | |||||
* | |||||
* @param string | |||||
* paintable ID | |||||
* @return RPC call target or null if none found | |||||
*/ | |||||
protected RpcTarget getRpcTarget(String string) { | |||||
// TODO improve this - VariableOwner and RpcManager separate? | |||||
VariableOwner owner = getVariableOwner(string); | |||||
if (owner instanceof RpcTarget) { | |||||
return (RpcTarget) owner; | |||||
} else { | |||||
return null; | |||||
} | |||||
return c; | |||||
} | } | ||||
private VariableOwner getDragAndDropService() { | |||||
private DragAndDropService getDragAndDropService() { | |||||
if (dragAndDropService == null) { | if (dragAndDropService == null) { | ||||
dragAndDropService = new DragAndDropService(this); | dragAndDropService = new DragAndDropService(this); | ||||
} | } | ||||
outWriter.print("for(;;);[{"); | outWriter.print("for(;;);[{"); | ||||
} | } | ||||
public Paintable getPaintable(String paintableId) { | |||||
return idPaintableMap.get(paintableId); | |||||
} | |||||
/** | |||||
* Gets the Paintable Id. If Paintable has debug id set it will be used | |||||
* prefixed with "PID_S". Otherwise a sequenced ID is created. | |||||
* | |||||
* @param paintable | |||||
* @return the paintable Id. | |||||
*/ | |||||
public String getPaintableId(Paintable paintable) { | |||||
String id = paintableIdMap.get(paintable); | |||||
if (id == null) { | |||||
// use testing identifier as id if set | |||||
id = paintable.getDebugId(); | |||||
if (id == null) { | |||||
id = "PID" + Integer.toString(idSequence++); | |||||
} else { | |||||
id = "PID_S" + id; | |||||
} | |||||
Paintable old = idPaintableMap.put(id, paintable); | |||||
if (old != null && old != paintable) { | |||||
/* | |||||
* Two paintables have the same id. We still make sure the old | |||||
* one is a component which is still attached to the | |||||
* application. This is just a precaution and should not be | |||||
* absolutely necessary. | |||||
*/ | |||||
if (old instanceof Component | |||||
&& ((Component) old).getApplication() != null) { | |||||
throw new IllegalStateException("Two paintables (" | |||||
+ paintable.getClass().getSimpleName() + "," | |||||
+ old.getClass().getSimpleName() | |||||
+ ") have been assigned the same id: " | |||||
+ paintable.getDebugId()); | |||||
} | |||||
} | |||||
paintableIdMap.put(paintable, id); | |||||
} | |||||
return id; | |||||
} | |||||
public boolean hasPaintableId(Paintable paintable) { | |||||
return paintableIdMap.containsKey(paintable); | |||||
} | |||||
/** | /** | ||||
* Returns dirty components which are in given window. Components in an | * Returns dirty components which are in given window. Components in an | ||||
* invisible subtrees are omitted. | * invisible subtrees are omitted. | ||||
* root window for which dirty components is to be fetched | * root window for which dirty components is to be fetched | ||||
* @return | * @return | ||||
*/ | */ | ||||
private ArrayList<Paintable> getDirtyVisibleComponents(Root r) { | |||||
final ArrayList<Paintable> resultset = new ArrayList<Paintable>( | |||||
dirtyPaintables); | |||||
// TODO mostly unnecessary? | |||||
// The following algorithm removes any components that would be painted | |||||
// as a direct descendant of other components from the dirty components | |||||
// list. The result is that each component should be painted exactly | |||||
// once and any unmodified components will be painted as "cached=true". | |||||
for (final Iterator<Paintable> i = dirtyPaintables.iterator(); i | |||||
.hasNext();) { | |||||
final Paintable p = i.next(); | |||||
if (p instanceof Component) { | |||||
final Component component = (Component) p; | |||||
if (component.getApplication() == null) { | |||||
// component is detached after requestRepaint is called | |||||
resultset.remove(p); | |||||
i.remove(); | |||||
} else { | |||||
Root componentsRoot = component.getRoot(); | |||||
if (componentsRoot == null) { | |||||
// This should not happen unless somebody has overriden | |||||
// getApplication or getWindow in an illegal way. | |||||
throw new IllegalStateException( | |||||
"component.getWindow() returned null for a component attached to the application"); | |||||
} | |||||
// if (componentsRoot.getParent() != null) { | |||||
// // this is a subwindow | |||||
// componentsRoot = componentsRoot.getParent(); | |||||
// } | |||||
if (componentsRoot != r) { | |||||
resultset.remove(p); | |||||
} else if (component.getParent() != null | |||||
&& !isVisible(component.getParent())) { | |||||
/* | |||||
* Do not return components in an invisible subtree. | |||||
* | |||||
* Components that are invisible in visible subree, must | |||||
* be rendered (to let client know that they need to be | |||||
* hidden). | |||||
*/ | |||||
resultset.remove(p); | |||||
} | |||||
} | |||||
private ArrayList<Component> getDirtyVisibleComponents( | |||||
DirtyConnectorTracker dirtyConnectorTracker) { | |||||
ArrayList<Component> dirtyComponents = new ArrayList<Component>(); | |||||
for (Component c : dirtyConnectorTracker.getDirtyComponents()) { | |||||
if (isVisible(c)) { | |||||
dirtyComponents.add(c); | |||||
} | } | ||||
} | } | ||||
return resultset; | |||||
return dirtyComponents; | |||||
} | } | ||||
/** | /** | ||||
* @see com.vaadin.terminal.Paintable.RepaintRequestListener#repaintRequested(com.vaadin.terminal.Paintable.RepaintRequestEvent) | * @see com.vaadin.terminal.Paintable.RepaintRequestListener#repaintRequested(com.vaadin.terminal.Paintable.RepaintRequestEvent) | ||||
*/ | */ | ||||
public void repaintRequested(RepaintRequestEvent event) { | public void repaintRequested(RepaintRequestEvent event) { | ||||
final Paintable p = event.getPaintable(); | |||||
if (!dirtyPaintables.contains(p)) { | |||||
dirtyPaintables.add(p); | |||||
} | |||||
// final Paintable p = event.getPaintable(); | |||||
// if (!dirtyPaintables.contains(p)) { | |||||
// dirtyPaintables.add(p); | |||||
// } | |||||
} | } | ||||
/** | /** | ||||
* @param paintable | * @param paintable | ||||
*/ | */ | ||||
private void paintablePainted(Paintable paintable) { | private void paintablePainted(Paintable paintable) { | ||||
dirtyPaintables.remove(paintable); | |||||
paintable.requestRepaintRequests(); | |||||
// dirtyPaintables.remove(paintable); | |||||
// paintable.requestRepaintRequests(); | |||||
} | } | ||||
/** | /** | ||||
} | } | ||||
} | } | ||||
/** | |||||
* Helper method to test if a component contains another | |||||
* | |||||
* @param parent | |||||
* @param child | |||||
*/ | |||||
private static boolean isChildOf(Component parent, Component child) { | |||||
Component p = child.getParent(); | |||||
while (p != null) { | |||||
if (parent == p) { | |||||
return true; | |||||
} | |||||
p = p.getParent(); | |||||
} | |||||
return false; | |||||
} | |||||
protected class InvalidUIDLSecurityKeyException extends | protected class InvalidUIDLSecurityKeyException extends | ||||
GeneralSecurityException { | GeneralSecurityException { | ||||
} | } | ||||
abstract String getStreamVariableTargetUrl(VariableOwner owner, | |||||
String name, StreamVariable value); | |||||
abstract String getStreamVariableTargetUrl(Connector owner, String name, | |||||
StreamVariable value); | |||||
abstract protected void cleanStreamVariable(VariableOwner owner, String name); | |||||
abstract protected void cleanStreamVariable(Connector owner, String name); | |||||
/** | /** | ||||
* Gets the bootstrap handler that should be used for generating the pages | * Gets the bootstrap handler that should be used for generating the pages | ||||
protected String getInitialUIDL(WrappedRequest request, Root root) | protected String getInitialUIDL(WrappedRequest request, Root root) | ||||
throws PaintException { | throws PaintException { | ||||
// TODO maybe unify writeUidlResponse()? | // TODO maybe unify writeUidlResponse()? | ||||
makeAllPaintablesDirty(root); | |||||
StringWriter sWriter = new StringWriter(); | StringWriter sWriter = new StringWriter(); | ||||
PrintWriter pWriter = new PrintWriter(sWriter); | PrintWriter pWriter = new PrintWriter(sWriter); | ||||
pWriter.print("{"); | pWriter.print("{"); | ||||
return b; | return b; | ||||
} | } | ||||
} | } | ||||
@Deprecated | |||||
public String getPaintableId(Paintable paintable) { | |||||
if (paintable instanceof Connector) { | |||||
return ((Connector) paintable).getConnectorId(); | |||||
} | |||||
throw new RuntimeException("Paintable " + paintable | |||||
+ " must implement Connector"); | |||||
} | |||||
} | } |
package com.vaadin.terminal.gwt.server; | |||||
import java.util.List; | |||||
import com.vaadin.terminal.gwt.client.Connector; | |||||
public interface ClientConnector extends Connector { | |||||
/** | |||||
* Returns the list of pending server to client RPC calls and clears the | |||||
* list. | |||||
* | |||||
* @return unmodifiable ordered list of pending server to client method | |||||
* calls (not null) | |||||
* | |||||
* @since 7.0 | |||||
*/ | |||||
public List<ClientMethodInvocation> retrievePendingRpcCalls(); | |||||
} |
import java.io.Serializable; | import java.io.Serializable; | ||||
import com.vaadin.terminal.Paintable; | |||||
/** | /** | ||||
* Internal class for keeping track of pending server to client method | * Internal class for keeping track of pending server to client method | ||||
* invocations for a Paintable. | |||||
* invocations for a Connector. | |||||
* | * | ||||
* @since 7.0 | * @since 7.0 | ||||
*/ | */ | ||||
public class ClientMethodInvocation implements Serializable, | public class ClientMethodInvocation implements Serializable, | ||||
Comparable<ClientMethodInvocation> { | Comparable<ClientMethodInvocation> { | ||||
private final Paintable paintable; | |||||
private final ClientConnector connector; | |||||
private final String interfaceName; | private final String interfaceName; | ||||
private final String methodName; | private final String methodName; | ||||
private final Object[] parameters; | private final Object[] parameters; | ||||
// TODO may cause problems when clustering etc. | // TODO may cause problems when clustering etc. | ||||
private static long counter = 0; | private static long counter = 0; | ||||
public ClientMethodInvocation(Paintable paintable, String interfaceName, | |||||
String methodName, Object[] parameters) { | |||||
this.paintable = paintable; | |||||
public ClientMethodInvocation(ClientConnector connector, | |||||
String interfaceName, String methodName, Object[] parameters) { | |||||
this.connector = connector; | |||||
this.interfaceName = interfaceName; | this.interfaceName = interfaceName; | ||||
this.methodName = methodName; | this.methodName = methodName; | ||||
this.parameters = (null != parameters) ? parameters : new Object[0]; | this.parameters = (null != parameters) ? parameters : new Object[0]; | ||||
sequenceNumber = ++counter; | sequenceNumber = ++counter; | ||||
} | } | ||||
public Paintable getPaintable() { | |||||
return paintable; | |||||
public ClientConnector getConnector() { | |||||
return connector; | |||||
} | } | ||||
public String getInterfaceName() { | public String getInterfaceName() { |
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.net.URL; | import java.net.URL; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Iterator; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.UUID; | import java.util.UUID; | ||||
import com.vaadin.Application; | import com.vaadin.Application; | ||||
import com.vaadin.terminal.PaintException; | import com.vaadin.terminal.PaintException; | ||||
import com.vaadin.terminal.Paintable; | |||||
import com.vaadin.terminal.StreamVariable; | import com.vaadin.terminal.StreamVariable; | ||||
import com.vaadin.terminal.VariableOwner; | |||||
import com.vaadin.terminal.WrappedRequest; | import com.vaadin.terminal.WrappedRequest; | ||||
import com.vaadin.terminal.WrappedResponse; | import com.vaadin.terminal.WrappedResponse; | ||||
import com.vaadin.ui.Component; | |||||
import com.vaadin.terminal.gwt.client.Connector; | |||||
import com.vaadin.ui.Root; | import com.vaadin.ui.Root; | ||||
/** | /** | ||||
/** | /** | ||||
* Handles file upload request submitted via Upload component. | * Handles file upload request submitted via Upload component. | ||||
* | * | ||||
* @param application | |||||
* | |||||
* @see #getStreamVariableTargetUrl(ReceiverOwner, String, StreamVariable) | * @see #getStreamVariableTargetUrl(ReceiverOwner, String, StreamVariable) | ||||
* | * | ||||
* @param request | * @param request | ||||
* @throws IOException | * @throws IOException | ||||
* @throws InvalidUIDLSecurityKeyException | * @throws InvalidUIDLSecurityKeyException | ||||
*/ | */ | ||||
public void handleFileUpload(WrappedRequest request, | |||||
WrappedResponse response) throws IOException, | |||||
InvalidUIDLSecurityKeyException { | |||||
public void handleFileUpload(Application application, | |||||
WrappedRequest request, WrappedResponse response) | |||||
throws IOException, InvalidUIDLSecurityKeyException { | |||||
/* | /* | ||||
* URI pattern: APP/UPLOAD/[PID]/[NAME]/[SECKEY] See #createReceiverUrl | * URI pattern: APP/UPLOAD/[PID]/[NAME]/[SECKEY] See #createReceiverUrl | ||||
String secKey = streamVariableToSeckey.get(streamVariable); | String secKey = streamVariableToSeckey.get(streamVariable); | ||||
if (secKey.equals(parts[2])) { | if (secKey.equals(parts[2])) { | ||||
VariableOwner source = getVariableOwner(paintableId); | |||||
Connector source = getConnector(application, paintableId); | |||||
String contentType = request.getContentType(); | String contentType = request.getContentType(); | ||||
if (contentType.contains("boundary")) { | if (contentType.contains("boundary")) { | ||||
// Multipart requests contain boundary string | // Multipart requests contain boundary string | ||||
} | } | ||||
@Override | @Override | ||||
protected void unregisterPaintable(Component p) { | |||||
/* Cleanup possible receivers */ | |||||
protected void postPaint(Root root) { | |||||
super.postPaint(root); | |||||
Application application = root.getApplication(); | |||||
if (pidToNameToStreamVariable != null) { | if (pidToNameToStreamVariable != null) { | ||||
Map<String, StreamVariable> removed = pidToNameToStreamVariable | |||||
.remove(getPaintableId(p)); | |||||
if (removed != null) { | |||||
for (String key : removed.keySet()) { | |||||
streamVariableToSeckey.remove(removed.get(key)); | |||||
Iterator<String> iterator = pidToNameToStreamVariable.keySet() | |||||
.iterator(); | |||||
while (iterator.hasNext()) { | |||||
String connectorId = iterator.next(); | |||||
if (application.getConnector(connectorId) == null) { | |||||
// Owner is no longer attached to the application | |||||
Map<String, StreamVariable> removed = pidToNameToStreamVariable | |||||
.get(connectorId); | |||||
for (String key : removed.keySet()) { | |||||
streamVariableToSeckey.remove(removed.get(key)); | |||||
} | |||||
iterator.remove(); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
super.unregisterPaintable(p); | |||||
} | } | ||||
private Map<StreamVariable, String> streamVariableToSeckey; | private Map<StreamVariable, String> streamVariableToSeckey; | ||||
@Override | @Override | ||||
String getStreamVariableTargetUrl(VariableOwner owner, String name, | |||||
String getStreamVariableTargetUrl(Connector owner, String name, | |||||
StreamVariable value) { | StreamVariable value) { | ||||
/* | /* | ||||
* We will use the same APP/* URI space as ApplicationResources but | * We will use the same APP/* URI space as ApplicationResources but | ||||
* NAME and PID from URI forms a key to fetch StreamVariable when | * NAME and PID from URI forms a key to fetch StreamVariable when | ||||
* handling post | * handling post | ||||
*/ | */ | ||||
String paintableId = getPaintableId((Paintable) owner); | |||||
String paintableId = owner.getConnectorId(); | |||||
String key = paintableId + "/" + name; | String key = paintableId + "/" + name; | ||||
if (pidToNameToStreamVariable == null) { | if (pidToNameToStreamVariable == null) { | ||||
} | } | ||||
@Override | @Override | ||||
protected void cleanStreamVariable(VariableOwner owner, String name) { | |||||
protected void cleanStreamVariable(Connector owner, String name) { | |||||
Map<String, StreamVariable> nameToStreamVar = pidToNameToStreamVariable | Map<String, StreamVariable> nameToStreamVar = pidToNameToStreamVariable | ||||
.get(getPaintableId((Paintable) owner)); | |||||
.get(owner.getConnectorId()); | |||||
nameToStreamVar.remove("name"); | nameToStreamVar.remove("name"); | ||||
if (nameToStreamVar.isEmpty()) { | if (nameToStreamVar.isEmpty()) { | ||||
pidToNameToStreamVariable.remove(getPaintableId((Paintable) owner)); | |||||
pidToNameToStreamVariable.remove(owner.getConnectorId()); | |||||
} | } | ||||
} | } | ||||
clientJSON.write("{"); | clientJSON.write("{"); | ||||
Component parent = component.getParent(); | Component parent = component.getParent(); | ||||
String paintableId = communicationManager.getPaintableId(component); | |||||
String paintableId = component.getConnectorId(); | |||||
clientJSON.print("id:\"" + paintableId + "\""); | clientJSON.print("id:\"" + paintableId + "\""); | ||||
import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; | import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; | ||||
import com.vaadin.terminal.PaintException; | import com.vaadin.terminal.PaintException; | ||||
import com.vaadin.terminal.VariableOwner; | import com.vaadin.terminal.VariableOwner; | ||||
import com.vaadin.terminal.gwt.client.Connector; | |||||
import com.vaadin.terminal.gwt.client.communication.SharedState; | |||||
import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; | |||||
import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager.DragEventType; | import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager.DragEventType; | ||||
import com.vaadin.ui.Component; | import com.vaadin.ui.Component; | ||||
public class DragAndDropService implements VariableOwner { | |||||
public class DragAndDropService implements VariableOwner, Connector { | |||||
private static final Logger logger = Logger | private static final Logger logger = Logger | ||||
.getLogger(DragAndDropService.class.getName()); | .getLogger(DragAndDropService.class.getName()); | ||||
} | } | ||||
public boolean isEnabled() { | public boolean isEnabled() { | ||||
return true; | |||||
return isConnectorEnabled(); | |||||
} | } | ||||
public boolean isImmediate() { | public boolean isImmediate() { | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
public SharedState getState() { | |||||
// TODO Auto-generated method stub | |||||
return null; | |||||
} | |||||
public String getConnectorId() { | |||||
return VDragAndDropManager.DD_SERVICE; | |||||
} | |||||
public boolean isConnectorEnabled() { | |||||
// Drag'n'drop can't be disabled | |||||
return true; | |||||
} | |||||
} | } |
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import com.vaadin.Application; | |||||
import com.vaadin.external.json.JSONArray; | import com.vaadin.external.json.JSONArray; | ||||
import com.vaadin.external.json.JSONException; | import com.vaadin.external.json.JSONException; | ||||
import com.vaadin.external.json.JSONObject; | import com.vaadin.external.json.JSONObject; | ||||
static { | static { | ||||
registerType(String.class, JsonEncoder.VTYPE_STRING); | registerType(String.class, JsonEncoder.VTYPE_STRING); | ||||
registerType(Paintable.class, JsonEncoder.VTYPE_PAINTABLE); | |||||
registerType(Connector.class, JsonEncoder.VTYPE_PAINTABLE); | |||||
registerType(Connector.class, JsonEncoder.VTYPE_CONNECTOR); | |||||
registerType(Boolean.class, JsonEncoder.VTYPE_BOOLEAN); | registerType(Boolean.class, JsonEncoder.VTYPE_BOOLEAN); | ||||
registerType(Integer.class, JsonEncoder.VTYPE_INTEGER); | registerType(Integer.class, JsonEncoder.VTYPE_INTEGER); | ||||
registerType(Float.class, JsonEncoder.VTYPE_FLOAT); | registerType(Float.class, JsonEncoder.VTYPE_FLOAT); | ||||
* | * | ||||
* @param value | * @param value | ||||
* JSON array with two elements | * JSON array with two elements | ||||
* @param idMapper | |||||
* @param application | |||||
* mapper between paintable ID and {@link Paintable} objects | * mapper between paintable ID and {@link Paintable} objects | ||||
* @return converted value (does not contain JSON types) | * @return converted value (does not contain JSON types) | ||||
* @throws JSONException | * @throws JSONException | ||||
* if the conversion fails | * if the conversion fails | ||||
*/ | */ | ||||
public static Object decode(JSONArray value, PaintableIdMapper idMapper) | |||||
public static Object decode(JSONArray value, Application application) | |||||
throws JSONException { | throws JSONException { | ||||
return decodeVariableValue(value.getString(0), value.get(1), idMapper); | |||||
return decodeVariableValue(value.getString(0), value.get(1), | |||||
application); | |||||
} | } | ||||
private static Object decodeVariableValue(String variableType, | private static Object decodeVariableValue(String variableType, | ||||
Object value, PaintableIdMapper idMapper) throws JSONException { | |||||
Object value, Application application) throws JSONException { | |||||
Object val = null; | Object val = null; | ||||
// TODO type checks etc. | // TODO type checks etc. | ||||
if (JsonEncoder.VTYPE_ARRAY.equals(variableType)) { | if (JsonEncoder.VTYPE_ARRAY.equals(variableType)) { | ||||
val = decodeArray((JSONArray) value, idMapper); | |||||
val = decodeArray((JSONArray) value, application); | |||||
} else if (JsonEncoder.VTYPE_LIST.equals(variableType)) { | } else if (JsonEncoder.VTYPE_LIST.equals(variableType)) { | ||||
val = decodeList((JSONArray) value, idMapper); | |||||
val = decodeList((JSONArray) value, application); | |||||
} else if (JsonEncoder.VTYPE_MAP.equals(variableType)) { | } else if (JsonEncoder.VTYPE_MAP.equals(variableType)) { | ||||
val = decodeMap((JSONObject) value, idMapper); | |||||
val = decodeMap((JSONObject) value, application); | |||||
} else if (JsonEncoder.VTYPE_STRINGARRAY.equals(variableType)) { | } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(variableType)) { | ||||
val = decodeStringArray((JSONArray) value); | val = decodeStringArray((JSONArray) value); | ||||
} else if (JsonEncoder.VTYPE_STRING.equals(variableType)) { | } else if (JsonEncoder.VTYPE_STRING.equals(variableType)) { | ||||
} else if (JsonEncoder.VTYPE_BOOLEAN.equals(variableType)) { | } else if (JsonEncoder.VTYPE_BOOLEAN.equals(variableType)) { | ||||
// TODO handle properly | // TODO handle properly | ||||
val = Boolean.valueOf(String.valueOf(value)); | val = Boolean.valueOf(String.valueOf(value)); | ||||
} else if (JsonEncoder.VTYPE_PAINTABLE.equals(variableType)) { | |||||
// TODO handle properly | |||||
val = idMapper.getPaintable(String.valueOf(value)); | |||||
} else if (JsonEncoder.VTYPE_CONNECTOR.equals(variableType)) { | |||||
val = application.getConnector(String.valueOf(value)); | |||||
} else if (JsonEncoder.VTYPE_NULL.equals(variableType)) { | } else if (JsonEncoder.VTYPE_NULL.equals(variableType)) { | ||||
val = null; | val = null; | ||||
} else { | } else { | ||||
// Try to decode object using fields | // Try to decode object using fields | ||||
return decodeObject(variableType, (JSONObject) value, idMapper); | |||||
return decodeObject(variableType, (JSONObject) value, application); | |||||
} | } | ||||
return val; | return val; | ||||
} | } | ||||
private static Object decodeMap(JSONObject jsonMap, | |||||
PaintableIdMapper idMapper) throws JSONException { | |||||
private static Object decodeMap(JSONObject jsonMap, Application application) | |||||
throws JSONException { | |||||
HashMap<String, Object> map = new HashMap<String, Object>(); | HashMap<String, Object> map = new HashMap<String, Object>(); | ||||
Iterator<String> it = jsonMap.keys(); | Iterator<String> it = jsonMap.keys(); | ||||
while (it.hasNext()) { | while (it.hasNext()) { | ||||
String key = it.next(); | String key = it.next(); | ||||
map.put(key, decode(jsonMap.getJSONArray(key), idMapper)); | |||||
map.put(key, decode(jsonMap.getJSONArray(key), application)); | |||||
} | } | ||||
return map; | return map; | ||||
} | } | ||||
} | } | ||||
private static Object decodeArray(JSONArray jsonArray, | private static Object decodeArray(JSONArray jsonArray, | ||||
PaintableIdMapper idMapper) throws JSONException { | |||||
List list = decodeList(jsonArray, idMapper); | |||||
Application application) throws JSONException { | |||||
List list = decodeList(jsonArray, application); | |||||
return list.toArray(new Object[list.size()]); | return list.toArray(new Object[list.size()]); | ||||
} | } | ||||
private static List decodeList(JSONArray jsonArray, | |||||
PaintableIdMapper idMapper) throws JSONException { | |||||
private static List decodeList(JSONArray jsonArray, Application application) | |||||
throws JSONException { | |||||
List<Object> list = new ArrayList<Object>(); | List<Object> list = new ArrayList<Object>(); | ||||
for (int i = 0; i < jsonArray.length(); ++i) { | for (int i = 0; i < jsonArray.length(); ++i) { | ||||
// each entry always has two elements: type and value | // each entry always has two elements: type and value | ||||
JSONArray entryArray = jsonArray.getJSONArray(i); | JSONArray entryArray = jsonArray.getJSONArray(i); | ||||
list.add(decode(entryArray, idMapper)); | |||||
list.add(decode(entryArray, application)); | |||||
} | } | ||||
return list; | return list; | ||||
} | } | ||||
* | * | ||||
* @param value | * @param value | ||||
* value to convert | * value to convert | ||||
* @param idMapper | |||||
* @param application | |||||
* mapper between paintable ID and {@link Paintable} objects | * mapper between paintable ID and {@link Paintable} objects | ||||
* @return JSON representation of the value | * @return JSON representation of the value | ||||
* @throws JSONException | * @throws JSONException | ||||
* if encoding a value fails (e.g. NaN or infinite number) | * if encoding a value fails (e.g. NaN or infinite number) | ||||
*/ | */ | ||||
public static JSONArray encode(Object value, PaintableIdMapper idMapper) | |||||
public static JSONArray encode(Object value, Application application) | |||||
throws JSONException { | throws JSONException { | ||||
return encode(value, null, idMapper); | |||||
return encode(value, null, application); | |||||
} | } | ||||
public static JSONArray encode(Object value, Class<?> valueType, | public static JSONArray encode(Object value, Class<?> valueType, | ||||
PaintableIdMapper idMapper) throws JSONException { | |||||
Application application) throws JSONException { | |||||
if (null == value) { | if (null == value) { | ||||
return combineTypeAndValue(JsonEncoder.VTYPE_NULL, JSONObject.NULL); | return combineTypeAndValue(JsonEncoder.VTYPE_NULL, JSONObject.NULL); | ||||
return combineTypeAndValue(getTransportType(value), value); | return combineTypeAndValue(getTransportType(value), value); | ||||
} else if (value instanceof List) { | } else if (value instanceof List) { | ||||
List list = (List) value; | List list = (List) value; | ||||
JSONArray jsonArray = encodeList(list, idMapper); | |||||
JSONArray jsonArray = encodeList(list, application); | |||||
return combineTypeAndValue(JsonEncoder.VTYPE_LIST, jsonArray); | return combineTypeAndValue(JsonEncoder.VTYPE_LIST, jsonArray); | ||||
} else if (value instanceof Object[]) { | } else if (value instanceof Object[]) { | ||||
Object[] array = (Object[]) value; | Object[] array = (Object[]) value; | ||||
JSONArray jsonArray = encodeArrayContents(array, idMapper); | |||||
JSONArray jsonArray = encodeArrayContents(array, application); | |||||
return combineTypeAndValue(JsonEncoder.VTYPE_ARRAY, jsonArray); | return combineTypeAndValue(JsonEncoder.VTYPE_ARRAY, jsonArray); | ||||
} else if (value instanceof Map) { | } else if (value instanceof Map) { | ||||
Map<String, Object> map = (Map<String, Object>) value; | Map<String, Object> map = (Map<String, Object>) value; | ||||
JSONObject jsonMap = encodeMapContents(map, idMapper); | |||||
JSONObject jsonMap = encodeMapContents(map, application); | |||||
return combineTypeAndValue(JsonEncoder.VTYPE_MAP, jsonMap); | return combineTypeAndValue(JsonEncoder.VTYPE_MAP, jsonMap); | ||||
} else if (value instanceof Paintable) { | |||||
Paintable paintable = (Paintable) value; | |||||
return combineTypeAndValue(JsonEncoder.VTYPE_PAINTABLE, | |||||
idMapper.getPaintableId(paintable)); | |||||
} else if (value instanceof Connector) { | |||||
Connector connector = (Connector) value; | |||||
return combineTypeAndValue(JsonEncoder.VTYPE_CONNECTOR, | |||||
connector.getConnectorId()); | |||||
} else if (getTransportType(value) != null) { | } else if (getTransportType(value) != null) { | ||||
return combineTypeAndValue(getTransportType(value), | return combineTypeAndValue(getTransportType(value), | ||||
String.valueOf(value)); | String.valueOf(value)); | ||||
} | } | ||||
return combineTypeAndValue(valueType.getCanonicalName(), | return combineTypeAndValue(valueType.getCanonicalName(), | ||||
encodeObject(value, idMapper)); | |||||
encodeObject(value, application)); | |||||
} | } | ||||
} | } | ||||
private static Object encodeObject(Object value, PaintableIdMapper idMapper) | |||||
private static Object encodeObject(Object value, Application application) | |||||
throws JSONException { | throws JSONException { | ||||
JSONObject jsonMap = new JSONObject(); | JSONObject jsonMap = new JSONObject(); | ||||
} | } | ||||
Method getterMethod = pd.getReadMethod(); | Method getterMethod = pd.getReadMethod(); | ||||
Object fieldValue = getterMethod.invoke(value, (Object[]) null); | Object fieldValue = getterMethod.invoke(value, (Object[]) null); | ||||
jsonMap.put(fieldName, encode(fieldValue, fieldType, idMapper)); | |||||
jsonMap.put(fieldName, | |||||
encode(fieldValue, fieldType, application)); | |||||
} | } | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
// TODO: Should exceptions be handled in a different way? | // TODO: Should exceptions be handled in a different way? | ||||
} | } | ||||
private static Object decodeObject(String type, | private static Object decodeObject(String type, | ||||
JSONObject serializedObject, PaintableIdMapper idMapper) | |||||
JSONObject serializedObject, Application application) | |||||
throws JSONException { | throws JSONException { | ||||
Class<?> cls; | Class<?> cls; | ||||
JSONArray encodedObject = serializedObject | JSONArray encodedObject = serializedObject | ||||
.getJSONArray(fieldName); | .getJSONArray(fieldName); | ||||
pd.getWriteMethod().invoke(decodedObject, | pd.getWriteMethod().invoke(decodedObject, | ||||
decode(encodedObject, idMapper)); | |||||
decode(encodedObject, application)); | |||||
} | } | ||||
return decodedObject; | return decodedObject; | ||||
} | } | ||||
private static JSONArray encodeArrayContents(Object[] array, | private static JSONArray encodeArrayContents(Object[] array, | ||||
PaintableIdMapper idMapper) throws JSONException { | |||||
Application application) throws JSONException { | |||||
JSONArray jsonArray = new JSONArray(); | JSONArray jsonArray = new JSONArray(); | ||||
for (Object o : array) { | for (Object o : array) { | ||||
// TODO handle object graph loops? | // TODO handle object graph loops? | ||||
jsonArray.put(encode(o, idMapper)); | |||||
jsonArray.put(encode(o, application)); | |||||
} | } | ||||
return jsonArray; | return jsonArray; | ||||
} | } | ||||
private static JSONArray encodeList(List list, PaintableIdMapper idMapper) | |||||
private static JSONArray encodeList(List list, Application application) | |||||
throws JSONException { | throws JSONException { | ||||
JSONArray jsonArray = new JSONArray(); | JSONArray jsonArray = new JSONArray(); | ||||
for (Object o : list) { | for (Object o : list) { | ||||
// TODO handle object graph loops? | // TODO handle object graph loops? | ||||
jsonArray.put(encode(o, idMapper)); | |||||
jsonArray.put(encode(o, application)); | |||||
} | } | ||||
return jsonArray; | return jsonArray; | ||||
} | } | ||||
private static JSONObject encodeMapContents(Map<String, Object> map, | private static JSONObject encodeMapContents(Map<String, Object> map, | ||||
PaintableIdMapper idMapper) throws JSONException { | |||||
Application application) throws JSONException { | |||||
JSONObject jsonMap = new JSONObject(); | JSONObject jsonMap = new JSONObject(); | ||||
for (String mapKey : map.keySet()) { | for (String mapKey : map.keySet()) { | ||||
// TODO handle object graph loops? | // TODO handle object graph loops? | ||||
Object mapValue = map.get(mapKey); | Object mapValue = map.get(mapKey); | ||||
jsonMap.put(mapKey, encode(mapValue, idMapper)); | |||||
jsonMap.put(mapKey, encode(mapValue, application)); | |||||
} | } | ||||
return jsonMap; | return jsonMap; | ||||
} | } |
import com.vaadin.terminal.StreamVariable; | import com.vaadin.terminal.StreamVariable; | ||||
import com.vaadin.terminal.ThemeResource; | import com.vaadin.terminal.ThemeResource; | ||||
import com.vaadin.terminal.VariableOwner; | import com.vaadin.terminal.VariableOwner; | ||||
import com.vaadin.terminal.gwt.client.Connector; | |||||
import com.vaadin.ui.Alignment; | import com.vaadin.ui.Alignment; | ||||
import com.vaadin.ui.ClientWidget; | import com.vaadin.ui.ClientWidget; | ||||
import com.vaadin.ui.Component; | |||||
import com.vaadin.ui.CustomLayout; | import com.vaadin.ui.CustomLayout; | ||||
import com.vaadin.ui.Root; | import com.vaadin.ui.Root; | ||||
private final Collection<Paintable> paintedComponents = new HashSet<Paintable>(); | private final Collection<Paintable> paintedComponents = new HashSet<Paintable>(); | ||||
private Collection<Paintable> identifiersCreatedDueRefPaint; | |||||
// private Collection<Paintable> identifiersCreatedDueRefPaint; | |||||
private Collection<Paintable> deferredPaintables; | private Collection<Paintable> deferredPaintables; | ||||
*/ | */ | ||||
public PaintStatus startPaintable(Paintable paintable, String tagName) | public PaintStatus startPaintable(Paintable paintable, String tagName) | ||||
throws PaintException { | throws PaintException { | ||||
boolean topLevelPaintable = openPaintables.isEmpty(); | |||||
System.out.println("startPaintable for " | |||||
+ paintable.getClass().getName() + "@" | |||||
+ Integer.toHexString(paintable.hashCode())); | |||||
startTag(tagName, true); | startTag(tagName, true); | ||||
final boolean isPreviouslyPainted = manager.hasPaintableId(paintable) | |||||
&& (identifiersCreatedDueRefPaint == null || !identifiersCreatedDueRefPaint | |||||
.contains(paintable)) | |||||
&& !deferredPaintables.contains(paintable); | |||||
openPaintables.push(paintable); | |||||
openPaintableTags.push(tagName); | |||||
final String id = manager.getPaintableId(paintable); | final String id = manager.getPaintableId(paintable); | ||||
paintable.addListener(manager); | paintable.addListener(manager); | ||||
addAttribute("id", id); | addAttribute("id", id); | ||||
// queue for painting later if already painting a paintable | // queue for painting later if already painting a paintable | ||||
boolean topLevelPaintableTag = openPaintables.isEmpty(); | |||||
openPaintables.push(paintable); | |||||
openPaintableTags.push(tagName); | |||||
if (!topLevelPaintableTag) { | |||||
if (!topLevelPaintable) { | |||||
// if (!deferredPaintables.contains(paintable)) { | |||||
// notify manager: add to paint queue instead of painting now | // notify manager: add to paint queue instead of painting now | ||||
manager.queuePaintable(paintable); | |||||
deferredPaintables.add(paintable); | |||||
// manager.queuePaintable(paintable); | |||||
// deferredPaintables.add(paintable); | |||||
// } | |||||
return PaintStatus.DEFER; | return PaintStatus.DEFER; | ||||
} else if (cacheEnabled && isPreviouslyPainted) { | |||||
// cached (unmodified) paintable, paint the it now | |||||
paintedComponents.add(paintable); | |||||
deferredPaintables.remove(paintable); | |||||
return PaintStatus.CACHED; | |||||
} else { | |||||
// not a nested paintable, paint the it now | |||||
paintedComponents.add(paintable); | |||||
deferredPaintables.remove(paintable); | |||||
} | |||||
if (paintable instanceof CustomLayout) { | |||||
customLayoutArgumentsOpen = true; | |||||
} | |||||
return PaintStatus.PAINTING; | |||||
// not a nested paintable, paint the it now | |||||
paintedComponents.add(paintable); | |||||
// deferredPaintables.remove(paintable); | |||||
if (paintable instanceof CustomLayout) { | |||||
customLayoutArgumentsOpen = true; | |||||
} | } | ||||
return PaintStatus.PAINTING; | |||||
} | } | ||||
public void endPaintable(Paintable paintable) throws PaintException { | public void endPaintable(Paintable paintable) throws PaintException { | ||||
Paintable openPaintable = openPaintables.peek(); | Paintable openPaintable = openPaintables.peek(); | ||||
if (paintable != openPaintable) { | if (paintable != openPaintable) { | ||||
throw new PaintException("Invalid UIDL: closing wrong paintable: '" | throw new PaintException("Invalid UIDL: closing wrong paintable: '" | ||||
+ getPaintIdentifier(paintable) + "' expected: '" | |||||
+ getPaintIdentifier(openPaintable) + "'."); | |||||
+ manager.getPaintableId(paintable) + "' expected: '" | |||||
+ manager.getPaintableId(openPaintable) + "'."); | |||||
} | } | ||||
// remove paintable from the stack | // remove paintable from the stack | ||||
openPaintables.pop(); | openPaintables.pop(); | ||||
} | } | ||||
public String getPaintIdentifier(Paintable paintable) throws PaintException { | public String getPaintIdentifier(Paintable paintable) throws PaintException { | ||||
if (!manager.hasPaintableId(paintable)) { | |||||
if (identifiersCreatedDueRefPaint == null) { | |||||
identifiersCreatedDueRefPaint = new HashSet<Paintable>(); | |||||
} | |||||
identifiersCreatedDueRefPaint.add(paintable); | |||||
} | |||||
// if (!manager.hasPaintableId(paintable)) { | |||||
// if (identifiersCreatedDueRefPaint == null) { | |||||
// identifiersCreatedDueRefPaint = new HashSet<Paintable>(); | |||||
// } | |||||
// identifiersCreatedDueRefPaint.add(paintable); | |||||
// } | |||||
return manager.getPaintableId(paintable); | return manager.getPaintableId(paintable); | ||||
} | } | ||||
return usedResources; | return usedResources; | ||||
} | } | ||||
/** | |||||
* Method to check if paintable is already painted into this target. | |||||
* | |||||
* @param p | |||||
* @return true if is not yet painted into this target and is connected to | |||||
* app | |||||
*/ | |||||
public boolean needsToBePainted(Paintable p) { | |||||
if (paintedComponents.contains(p)) { | |||||
return false; | |||||
} else if (((Component) p).getApplication() == null) { | |||||
return false; | |||||
} else { | |||||
return true; | |||||
} | |||||
} | |||||
// /** | |||||
// * Method to check if paintable is already painted into this target. | |||||
// * | |||||
// * @param p | |||||
// * @return true if is not yet painted into this target and is connected to | |||||
// * app | |||||
// */ | |||||
// public boolean needsToBePainted(Paintable p) { | |||||
// if (paintedComponents.contains(p)) { | |||||
// return false; | |||||
// } else if (((Component) p).getApplication() == null) { | |||||
// return false; | |||||
// } else { | |||||
// return true; | |||||
// } | |||||
// } | |||||
private static final Map<Class<? extends Paintable>, Class<? extends Paintable>> widgetMappingCache = new HashMap<Class<? extends Paintable>, Class<? extends Paintable>>(); | private static final Map<Class<? extends Paintable>, Class<? extends Paintable>> widgetMappingCache = new HashMap<Class<? extends Paintable>, Class<? extends Paintable>>(); | ||||
public void addVariable(VariableOwner owner, String name, | public void addVariable(VariableOwner owner, String name, | ||||
StreamVariable value) throws PaintException { | StreamVariable value) throws PaintException { | ||||
String url = manager.getStreamVariableTargetUrl(owner, name, value); | |||||
String url = manager.getStreamVariableTargetUrl((Connector) owner, | |||||
name, value); | |||||
if (url != null) { | if (url != null) { | ||||
addVariable(owner, name, url); | addVariable(owner, name, url); | ||||
} // else { //NOP this was just a cleanup by component } | } // else { //NOP this was just a cleanup by component } |
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Iterator; | |||||
import java.util.Map; | import java.util.Map; | ||||
import javax.portlet.MimeResponse; | import javax.portlet.MimeResponse; | ||||
import com.vaadin.external.json.JSONObject; | import com.vaadin.external.json.JSONObject; | ||||
import com.vaadin.terminal.DeploymentConfiguration; | import com.vaadin.terminal.DeploymentConfiguration; | ||||
import com.vaadin.terminal.PaintException; | import com.vaadin.terminal.PaintException; | ||||
import com.vaadin.terminal.Paintable; | |||||
import com.vaadin.terminal.StreamVariable; | import com.vaadin.terminal.StreamVariable; | ||||
import com.vaadin.terminal.VariableOwner; | |||||
import com.vaadin.terminal.WrappedRequest; | import com.vaadin.terminal.WrappedRequest; | ||||
import com.vaadin.terminal.WrappedResponse; | import com.vaadin.terminal.WrappedResponse; | ||||
import com.vaadin.ui.Component; | |||||
import com.vaadin.terminal.gwt.client.Connector; | |||||
import com.vaadin.ui.Root; | import com.vaadin.ui.Root; | ||||
/** | /** | ||||
String contentType = request.getContentType(); | String contentType = request.getContentType(); | ||||
String name = request.getParameter("name"); | String name = request.getParameter("name"); | ||||
String ownerId = request.getParameter("rec-owner"); | String ownerId = request.getParameter("rec-owner"); | ||||
VariableOwner variableOwner = getVariableOwner(ownerId); | |||||
StreamVariable streamVariable = ownerToNameToStreamVariable.get( | |||||
variableOwner).get(name); | |||||
Connector owner = getConnector(getApplication(), ownerId); | |||||
StreamVariable streamVariable = ownerToNameToStreamVariable.get(owner) | |||||
.get(name); | |||||
if (contentType.contains("boundary")) { | if (contentType.contains("boundary")) { | ||||
doHandleSimpleMultipartFileUpload(request, response, | doHandleSimpleMultipartFileUpload(request, response, | ||||
streamVariable, name, variableOwner, | |||||
streamVariable, name, owner, | |||||
contentType.split("boundary=")[1]); | contentType.split("boundary=")[1]); | ||||
} else { | } else { | ||||
doHandleXhrFilePost(request, response, streamVariable, name, | |||||
variableOwner, request.getContentLength()); | |||||
doHandleXhrFilePost(request, response, streamVariable, name, owner, | |||||
request.getContentLength()); | |||||
} | } | ||||
} | } | ||||
@Override | @Override | ||||
protected void unregisterPaintable(Component p) { | |||||
super.unregisterPaintable(p); | |||||
protected void postPaint(Root root) { | |||||
super.postPaint(root); | |||||
Application application = root.getApplication(); | |||||
if (ownerToNameToStreamVariable != null) { | if (ownerToNameToStreamVariable != null) { | ||||
ownerToNameToStreamVariable.remove(p); | |||||
Iterator<Connector> iterator = ownerToNameToStreamVariable.keySet() | |||||
.iterator(); | |||||
while (iterator.hasNext()) { | |||||
Connector owner = iterator.next(); | |||||
if (application.getConnector(owner.getConnectorId()) == null) { | |||||
// Owner is no longer attached to the application | |||||
iterator.remove(); | |||||
} | |||||
} | |||||
} | } | ||||
} | } | ||||
currentUidlResponse = null; | currentUidlResponse = null; | ||||
} | } | ||||
private Map<VariableOwner, Map<String, StreamVariable>> ownerToNameToStreamVariable; | |||||
private Map<Connector, Map<String, StreamVariable>> ownerToNameToStreamVariable; | |||||
@Override | @Override | ||||
String getStreamVariableTargetUrl(VariableOwner owner, String name, | |||||
String getStreamVariableTargetUrl(Connector owner, String name, | |||||
StreamVariable value) { | StreamVariable value) { | ||||
if (ownerToNameToStreamVariable == null) { | if (ownerToNameToStreamVariable == null) { | ||||
ownerToNameToStreamVariable = new HashMap<VariableOwner, Map<String, StreamVariable>>(); | |||||
ownerToNameToStreamVariable = new HashMap<Connector, Map<String, StreamVariable>>(); | |||||
} | } | ||||
Map<String, StreamVariable> nameToReceiver = ownerToNameToStreamVariable | Map<String, StreamVariable> nameToReceiver = ownerToNameToStreamVariable | ||||
.get(owner); | .get(owner); | ||||
ResourceURL resurl = currentUidlResponse.createResourceURL(); | ResourceURL resurl = currentUidlResponse.createResourceURL(); | ||||
resurl.setResourceID("UPLOAD"); | resurl.setResourceID("UPLOAD"); | ||||
resurl.setParameter("name", name); | resurl.setParameter("name", name); | ||||
resurl.setParameter("rec-owner", getPaintableId((Paintable) owner)); | |||||
resurl.setParameter("rec-owner", owner.getConnectorId()); | |||||
resurl.setProperty("name", name); | resurl.setProperty("name", name); | ||||
resurl.setProperty("rec-owner", getPaintableId((Paintable) owner)); | |||||
resurl.setProperty("rec-owner", owner.getConnectorId()); | |||||
return resurl.toString(); | return resurl.toString(); | ||||
} | } | ||||
@Override | @Override | ||||
protected void cleanStreamVariable(VariableOwner owner, String name) { | |||||
protected void cleanStreamVariable(Connector owner, String name) { | |||||
Map<String, StreamVariable> map = ownerToNameToStreamVariable | Map<String, StreamVariable> map = ownerToNameToStreamVariable | ||||
.get(owner); | .get(owner); | ||||
map.remove(name); | map.remove(name); |
*/ | */ | ||||
private ArrayList<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>(); | private ArrayList<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>(); | ||||
private String connectorId; | |||||
/* Constructor */ | /* Constructor */ | ||||
/** | /** | ||||
* interface. | * interface. | ||||
*/ | */ | ||||
public void attach() { | public void attach() { | ||||
getRoot().componentAttached(this); | |||||
requestRepaint(); | requestRepaint(); | ||||
if (!getState().isVisible()) { | if (!getState().isVisible()) { | ||||
/* | /* | ||||
// compiler happy | // compiler happy | ||||
actionManager.setViewer((Root) null); | actionManager.setViewer((Root) null); | ||||
} | } | ||||
getRoot().componentDetached(this); | |||||
} | } | ||||
/** | /** | ||||
} | } | ||||
public String getConnectorId() { | public String getConnectorId() { | ||||
throw new RuntimeException( | |||||
"TODO: Move connector id handling to AbstractComponent"); | |||||
if (connectorId == null) { | |||||
if (getApplication() == null) { | |||||
throw new RuntimeException( | |||||
"Component must be attached to an application when getConnectorId() is called for the first time"); | |||||
} | |||||
connectorId = getApplication().createConnectorId(this); | |||||
} | |||||
return connectorId; | |||||
} | } | ||||
} | } |
import com.vaadin.terminal.Sizeable; | import com.vaadin.terminal.Sizeable; | ||||
import com.vaadin.terminal.VariableOwner; | import com.vaadin.terminal.VariableOwner; | ||||
import com.vaadin.terminal.gwt.client.ComponentState; | import com.vaadin.terminal.gwt.client.ComponentState; | ||||
import com.vaadin.terminal.gwt.client.Connector; | |||||
import com.vaadin.terminal.gwt.server.ClientConnector; | |||||
import com.vaadin.terminal.gwt.server.RpcTarget; | import com.vaadin.terminal.gwt.server.RpcTarget; | ||||
/** | /** | ||||
* @VERSION@ | * @VERSION@ | ||||
* @since 3.0 | * @since 3.0 | ||||
*/ | */ | ||||
public interface Component extends Connector, Paintable, VariableOwner, | |||||
public interface Component extends ClientConnector, Paintable, VariableOwner, | |||||
Sizeable, Serializable, RpcTarget { | Sizeable, Serializable, RpcTarget { | ||||
/** | /** |
package com.vaadin.ui; | |||||
import java.util.Collection; | |||||
import java.util.HashSet; | |||||
import java.util.Set; | |||||
import java.util.logging.Logger; | |||||
import com.vaadin.terminal.Paintable.RepaintRequestEvent; | |||||
import com.vaadin.terminal.Paintable.RepaintRequestListener; | |||||
public class DirtyConnectorTracker implements RepaintRequestListener { | |||||
private Set<Component> dirtyComponents = new HashSet<Component>(); | |||||
private Root root; | |||||
public static Logger getLogger() { | |||||
return Logger.getLogger(DirtyConnectorTracker.class.getName()); | |||||
} | |||||
public DirtyConnectorTracker(Root root) { | |||||
this.root = root; | |||||
} | |||||
public void repaintRequested(RepaintRequestEvent event) { | |||||
markDirty((Component) event.getPaintable()); | |||||
} | |||||
public void componentAttached(Component component) { | |||||
component.addListener(this); | |||||
markDirty(component); | |||||
} | |||||
private void markDirty(Component component) { | |||||
// TODO Remove debug info | |||||
if (!dirtyComponents.contains(component)) { | |||||
debug(component, "is now dirty"); | |||||
} | |||||
dirtyComponents.add(component); | |||||
} | |||||
private void debug(Component component, String string) { | |||||
getLogger().info(getDebugInfo(component) + " " + string); | |||||
} | |||||
private void markClean(Component component) { | |||||
// TODO Remove debug info | |||||
if (dirtyComponents.contains(component)) { | |||||
debug(component, "is no longer dirty"); | |||||
} | |||||
dirtyComponents.remove(component); | |||||
// TODO .... WTF .... | |||||
component.requestRepaintRequests(); | |||||
} | |||||
private String getDebugInfo(Component component) { | |||||
String message = getObjectString(component); | |||||
if (component.getParent() != null) { | |||||
message += " (parent: " + getObjectString(component.getParent()) | |||||
+ ")"; | |||||
} | |||||
return message; | |||||
} | |||||
private String getObjectString(Object component) { | |||||
return component.getClass().getName() + "@" | |||||
+ Integer.toHexString(component.hashCode()); | |||||
} | |||||
public void componentDetached(Component component) { | |||||
component.removeListener(this); | |||||
markClean(component); | |||||
} | |||||
public void markAllComponentsDirty() { | |||||
markComponentsDirtyRecursively(root); | |||||
System.out.println("All components are now dirty"); | |||||
} | |||||
public void markAllComponentsClean() { | |||||
dirtyComponents.clear(); | |||||
System.out.println("All components are now clean"); | |||||
} | |||||
private void markComponentsDirtyRecursively(Component c) { | |||||
markDirty(c); | |||||
if (c instanceof HasComponents) { | |||||
HasComponents container = (HasComponents) c; | |||||
for (Component child : container) { | |||||
markComponentsDirtyRecursively(child); | |||||
} | |||||
} | |||||
} | |||||
public Collection<Component> getDirtyComponents() { | |||||
return dirtyComponents; | |||||
} | |||||
} |
/** Identifies the click event */ | /** Identifies the click event */ | ||||
private static final String CLICK_EVENT_ID = VView.CLICK_EVENT_ID; | private static final String CLICK_EVENT_ID = VView.CLICK_EVENT_ID; | ||||
private DirtyConnectorTracker dirtyConnectorTracker = new DirtyConnectorTracker( | |||||
this); | |||||
/** | /** | ||||
* Creates a new empty root without a caption. This root will have a | * Creates a new empty root without a caption. This root will have a | ||||
* {@link VerticalLayout} with margins enabled as its content. | * {@link VerticalLayout} with margins enabled as its content. | ||||
// TODO How can a Root be invisible? What does it mean? | // TODO How can a Root be invisible? What does it mean? | ||||
return isVisible() && isEnabled(); | return isVisible() && isEnabled(); | ||||
} | } | ||||
public DirtyConnectorTracker getDirtyConnectorTracker() { | |||||
return dirtyConnectorTracker; | |||||
} | |||||
public void componentAttached(Component component) { | |||||
getDirtyConnectorTracker().componentAttached(component); | |||||
} | |||||
public void componentDetached(Component component) { | |||||
getDirtyConnectorTracker().componentDetached(component); | |||||
} | |||||
} | } |