You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

JavaScriptConnectorHelper.java 13KB

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