Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

JavaScriptConnectorHelper.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client;
  5. import java.util.ArrayList;
  6. import java.util.HashMap;
  7. import java.util.HashSet;
  8. import java.util.Map;
  9. import java.util.Map.Entry;
  10. import java.util.Set;
  11. import com.google.gwt.core.client.JavaScriptObject;
  12. import com.google.gwt.core.client.JsArray;
  13. import com.google.gwt.json.client.JSONArray;
  14. import com.google.gwt.user.client.Element;
  15. import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
  16. import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
  17. import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler;
  18. public class JavaScriptConnectorHelper {
  19. public interface JavaScriptConnectorState {
  20. public Set<String> getCallbackNames();
  21. public Map<String, Set<String>> getRpcInterfaces();
  22. }
  23. private final ServerConnector connector;
  24. private final JavaScriptObject nativeState = JavaScriptObject
  25. .createObject();
  26. private final JavaScriptObject rpcMap = JavaScriptObject.createObject();
  27. private final Map<String, JavaScriptObject> rpcObjects = new HashMap<String, JavaScriptObject>();
  28. private final Map<String, Set<String>> rpcMethods = new HashMap<String, Set<String>>();
  29. private JavaScriptObject connectorWrapper;
  30. private int tag;
  31. private boolean inited = false;
  32. public JavaScriptConnectorHelper(ServerConnector connector) {
  33. this.connector = connector;
  34. // Wildcard rpc object
  35. rpcObjects.put("", JavaScriptObject.createObject());
  36. }
  37. public void init() {
  38. connector.addStateChangeHandler(new StateChangeHandler() {
  39. @Override
  40. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  41. JavaScriptObject wrapper = getConnectorWrapper();
  42. JavaScriptConnectorState state = getConnectorState();
  43. for (String callback : state.getCallbackNames()) {
  44. ensureCallback(JavaScriptConnectorHelper.this, wrapper,
  45. callback);
  46. }
  47. for (Entry<String, Set<String>> entry : state
  48. .getRpcInterfaces().entrySet()) {
  49. String rpcName = entry.getKey();
  50. String jsName = getJsInterfaceName(rpcName);
  51. if (!rpcObjects.containsKey(jsName)) {
  52. Set<String> methods = entry.getValue();
  53. rpcObjects.put(jsName,
  54. createRpcObject(rpcName, methods));
  55. // Init all methods for wildcard rpc
  56. for (String method : methods) {
  57. JavaScriptObject wildcardRpcObject = rpcObjects
  58. .get("");
  59. Set<String> interfaces = rpcMethods.get(method);
  60. if (interfaces == null) {
  61. interfaces = new HashSet<String>();
  62. rpcMethods.put(method, interfaces);
  63. attachRpcMethod(wildcardRpcObject, null, method);
  64. }
  65. interfaces.add(rpcName);
  66. }
  67. }
  68. }
  69. // Init after setting up callbacks & rpc
  70. if (!inited) {
  71. initJavaScript();
  72. inited = true;
  73. }
  74. fireNativeStateChange(wrapper);
  75. }
  76. });
  77. }
  78. private static String getJsInterfaceName(String rpcName) {
  79. return rpcName.replace('$', '.');
  80. }
  81. protected JavaScriptObject createRpcObject(String iface, Set<String> methods) {
  82. JavaScriptObject object = JavaScriptObject.createObject();
  83. for (String method : methods) {
  84. attachRpcMethod(object, iface, method);
  85. }
  86. return object;
  87. }
  88. private boolean initJavaScript() {
  89. ApplicationConfiguration conf = connector.getConnection()
  90. .getConfiguration();
  91. ArrayList<String> attemptedNames = new ArrayList<String>();
  92. Integer tag = Integer.valueOf(this.tag);
  93. while (tag != null) {
  94. String serverSideClassName = conf.getServerSideClassNameForTag(tag);
  95. String initFunctionName = serverSideClassName
  96. .replaceAll("\\.", "_");
  97. if (tryInitJs(initFunctionName, getConnectorWrapper())) {
  98. VConsole.log("JavaScript connector initialized using "
  99. + initFunctionName);
  100. return true;
  101. } else {
  102. VConsole.log("No JavaScript function " + initFunctionName
  103. + " found");
  104. attemptedNames.add(initFunctionName);
  105. tag = conf.getParentTag(tag.intValue());
  106. }
  107. }
  108. VConsole.log("No JavaScript init for connector not found");
  109. showInitProblem(attemptedNames);
  110. return false;
  111. }
  112. protected void showInitProblem(ArrayList<String> attemptedNames) {
  113. // Default does nothing
  114. }
  115. private static native boolean tryInitJs(String initFunctionName,
  116. JavaScriptObject connectorWrapper)
  117. /*-{
  118. if (typeof $wnd[initFunctionName] == 'function') {
  119. $wnd[initFunctionName].apply(connectorWrapper);
  120. return true;
  121. } else {
  122. return false;
  123. }
  124. }-*/;
  125. private JavaScriptObject getConnectorWrapper() {
  126. if (connectorWrapper == null) {
  127. connectorWrapper = createConnectorWrapper(this, nativeState,
  128. rpcMap, connector.getConnectorId(), rpcObjects);
  129. }
  130. return connectorWrapper;
  131. }
  132. private static native void fireNativeStateChange(
  133. JavaScriptObject connectorWrapper)
  134. /*-{
  135. if (typeof connectorWrapper.onStateChange == 'function') {
  136. connectorWrapper.onStateChange();
  137. }
  138. }-*/;
  139. private static native JavaScriptObject createConnectorWrapper(
  140. JavaScriptConnectorHelper h, JavaScriptObject nativeState,
  141. JavaScriptObject registeredRpc, String connectorId,
  142. Map<String, JavaScriptObject> rpcObjects)
  143. /*-{
  144. return {
  145. 'getConnectorId': function() {
  146. return connectorId;
  147. },
  148. 'getParentId': $entry(function(connectorId) {
  149. return h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::getParentId(Ljava/lang/String;)(connectorId);
  150. }),
  151. 'getState': function() {
  152. return nativeState;
  153. },
  154. 'getRpcProxy': $entry(function(iface) {
  155. if (!iface) {
  156. iface = '';
  157. }
  158. return rpcObjects.@java.util.Map::get(Ljava/lang/Object;)(iface);
  159. }),
  160. 'getElement': $entry(function(connectorId) {
  161. return h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::getWidgetElement(Ljava/lang/String;)(connectorId);
  162. }),
  163. 'registerRpc': function(iface, rpcHandler) {
  164. //registerRpc(handler) -> registerRpc('', handler);
  165. if (!rpcHandler) {
  166. rpcHandler = iface;
  167. iface = '';
  168. }
  169. if (!registeredRpc[iface]) {
  170. registeredRpc[iface] = [];
  171. }
  172. registeredRpc[iface].push(rpcHandler);
  173. },
  174. };
  175. }-*/;
  176. private native void attachRpcMethod(JavaScriptObject rpc, String iface,
  177. String method)
  178. /*-{
  179. var self = this;
  180. rpc[method] = $entry(function() {
  181. self.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, arguments);
  182. });
  183. }-*/;
  184. private String getParentId(String connectorId) {
  185. ServerConnector target = getConnector(connectorId);
  186. if (target == null) {
  187. return null;
  188. }
  189. ServerConnector parent = target.getParent();
  190. if (parent == null) {
  191. return null;
  192. } else {
  193. return parent.getConnectorId();
  194. }
  195. }
  196. private Element getWidgetElement(String connectorId) {
  197. ServerConnector target = getConnector(connectorId);
  198. if (target instanceof ComponentConnector) {
  199. return ((ComponentConnector) target).getWidget().getElement();
  200. } else {
  201. return null;
  202. }
  203. }
  204. private ServerConnector getConnector(String connectorId) {
  205. if (connectorId == null || connectorId.length() == 0) {
  206. return connector;
  207. }
  208. return ConnectorMap.get(connector.getConnection()).getConnector(
  209. connectorId);
  210. }
  211. private void fireRpc(String iface, String method,
  212. JsArray<JavaScriptObject> arguments) {
  213. if (iface == null) {
  214. iface = findWildcardInterface(method);
  215. }
  216. JSONArray argumentsArray = new JSONArray(arguments);
  217. Object[] parameters = new Object[arguments.length()];
  218. for (int i = 0; i < parameters.length; i++) {
  219. parameters[i] = argumentsArray.get(i);
  220. }
  221. connector.getConnection().addMethodInvocationToQueue(
  222. new MethodInvocation(connector.getConnectorId(), iface, method,
  223. parameters), true);
  224. }
  225. private String findWildcardInterface(String method) {
  226. Set<String> interfaces = rpcMethods.get(method);
  227. if (interfaces.size() == 1) {
  228. return interfaces.iterator().next();
  229. } else {
  230. // TODO Resolve conflicts using argument count and types
  231. String interfaceList = "";
  232. for (String iface : interfaces) {
  233. if (interfaceList.length() != 0) {
  234. interfaceList += ", ";
  235. }
  236. interfaceList += getJsInterfaceName(iface);
  237. }
  238. throw new IllegalStateException(
  239. "Can not call method "
  240. + method
  241. + " for wildcard rpc proxy because the function is defined for multiple rpc interfaces: "
  242. + interfaceList
  243. + ". Retrieve a rpc proxy for a specific interface using getRpcProxy(interfaceName) to use the function.");
  244. }
  245. }
  246. private void fireCallback(String name, JsArray<JavaScriptObject> arguments) {
  247. MethodInvocation invocation = new MethodInvocation(
  248. connector.getConnectorId(),
  249. "com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", "call",
  250. new Object[] { name, new JSONArray(arguments) });
  251. connector.getConnection().addMethodInvocationToQueue(invocation, true);
  252. }
  253. public void setNativeState(JavaScriptObject state) {
  254. updateNativeState(nativeState, state);
  255. }
  256. private static native void updateNativeState(JavaScriptObject state,
  257. JavaScriptObject input)
  258. /*-{
  259. // Copy all fields to existing state object
  260. for(var key in state) {
  261. if (state.hasOwnProperty(key)) {
  262. delete state[key];
  263. }
  264. }
  265. for(var key in input) {
  266. if (input.hasOwnProperty(key)) {
  267. state[key] = input[key];
  268. }
  269. }
  270. }-*/;
  271. public Object[] decodeRpcParameters(JSONArray parametersJson) {
  272. return new Object[] { parametersJson.getJavaScriptObject() };
  273. }
  274. public void setTag(int tag) {
  275. this.tag = tag;
  276. }
  277. public void invokeJsRpc(MethodInvocation invocation,
  278. JSONArray parametersJson) {
  279. String iface = invocation.getInterfaceName();
  280. String method = invocation.getMethodName();
  281. if ("com.vaadin.ui.JavaScript$JavaScriptCallbackRpc".equals(iface)
  282. && "call".equals(method)) {
  283. String callbackName = parametersJson.get(0).isString()
  284. .stringValue();
  285. JavaScriptObject arguments = parametersJson.get(1).isArray()
  286. .getJavaScriptObject();
  287. invokeCallback(getConnectorWrapper(), callbackName, arguments);
  288. } else {
  289. JavaScriptObject arguments = parametersJson.getJavaScriptObject();
  290. invokeJsRpc(rpcMap, iface, method, arguments, getConnectorWrapper());
  291. // Also invoke wildcard interface
  292. invokeJsRpc(rpcMap, "", method, arguments, getConnectorWrapper());
  293. }
  294. }
  295. private static native void invokeCallback(JavaScriptObject connector,
  296. String name, JavaScriptObject arguments)
  297. /*-{
  298. connector[name].apply(connector, arguments);
  299. }-*/;
  300. private static native void invokeJsRpc(JavaScriptObject rpcMap,
  301. String interfaceName, String methodName,
  302. JavaScriptObject parameters, JavaScriptObject connector)
  303. /*-{
  304. var targets = rpcMap[interfaceName];
  305. if (!targets) {
  306. return;
  307. }
  308. for(var i = 0; i < targets.length; i++) {
  309. var target = targets[i];
  310. target[methodName].apply(connector, parameters);
  311. }
  312. }-*/;
  313. private static native void ensureCallback(JavaScriptConnectorHelper h,
  314. JavaScriptObject connector, String name)
  315. /*-{
  316. connector[name] = $entry(function() {
  317. var args = Array.prototype.slice.call(arguments, 0);
  318. h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::fireCallback(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args);
  319. });
  320. }-*/;
  321. private JavaScriptConnectorState getConnectorState() {
  322. return (JavaScriptConnectorState) connector.getState();
  323. }
  324. }