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.

AbstractClientConnector.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal;
  5. import java.io.Serializable;
  6. import java.lang.reflect.Constructor;
  7. import java.lang.reflect.InvocationHandler;
  8. import java.lang.reflect.Method;
  9. import java.lang.reflect.Proxy;
  10. import java.util.ArrayList;
  11. import java.util.Collection;
  12. import java.util.Collections;
  13. import java.util.HashMap;
  14. import java.util.Iterator;
  15. import java.util.List;
  16. import java.util.Map;
  17. import java.util.NoSuchElementException;
  18. import java.util.logging.Logger;
  19. import com.vaadin.Application;
  20. import com.vaadin.terminal.gwt.client.communication.ClientRpc;
  21. import com.vaadin.terminal.gwt.client.communication.ServerRpc;
  22. import com.vaadin.terminal.gwt.client.communication.SharedState;
  23. import com.vaadin.terminal.gwt.server.ClientConnector;
  24. import com.vaadin.terminal.gwt.server.ClientMethodInvocation;
  25. import com.vaadin.terminal.gwt.server.RpcManager;
  26. import com.vaadin.terminal.gwt.server.RpcTarget;
  27. import com.vaadin.terminal.gwt.server.ServerRpcManager;
  28. import com.vaadin.ui.HasComponents;
  29. import com.vaadin.ui.Root;
  30. /**
  31. * An abstract base class for ClientConnector implementations. This class
  32. * provides all the basic functionality required for connectors.
  33. *
  34. * @author Vaadin Ltd
  35. * @version @VERSION@
  36. * @since 7.0.0
  37. */
  38. public abstract class AbstractClientConnector implements ClientConnector {
  39. /**
  40. * A map from client to server RPC interface class to the RPC call manager
  41. * that handles incoming RPC calls for that interface.
  42. */
  43. private Map<Class<?>, RpcManager> rpcManagerMap = new HashMap<Class<?>, RpcManager>();
  44. /**
  45. * A map from server to client RPC interface class to the RPC proxy that
  46. * sends ourgoing RPC calls for that interface.
  47. */
  48. private Map<Class<?>, ClientRpc> rpcProxyMap = new HashMap<Class<?>, ClientRpc>();
  49. /**
  50. * Shared state object to be communicated from the server to the client when
  51. * modified.
  52. */
  53. private SharedState sharedState;
  54. /**
  55. * Pending RPC method invocations to be sent.
  56. */
  57. private ArrayList<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>();
  58. private String connectorId;
  59. private ArrayList<Extension> extensions = new ArrayList<Extension>();
  60. private ClientConnector parent;
  61. /* Documentation copied from interface */
  62. @Override
  63. public void requestRepaint() {
  64. Root root = getRoot();
  65. if (root != null) {
  66. root.getConnectorTracker().markDirty(this);
  67. }
  68. }
  69. /**
  70. * Registers an RPC interface implementation for this component.
  71. *
  72. * A component can listen to multiple RPC interfaces, and subclasses can
  73. * register additional implementations.
  74. *
  75. * @since 7.0
  76. *
  77. * @param implementation
  78. * RPC interface implementation
  79. * @param rpcInterfaceType
  80. * RPC interface class for which the implementation should be
  81. * registered
  82. */
  83. protected <T> void registerRpc(T implementation, Class<T> rpcInterfaceType) {
  84. rpcManagerMap.put(rpcInterfaceType, new ServerRpcManager<T>(
  85. implementation, rpcInterfaceType));
  86. }
  87. /**
  88. * Registers an RPC interface implementation for this component.
  89. *
  90. * A component can listen to multiple RPC interfaces, and subclasses can
  91. * register additional implementations.
  92. *
  93. * @since 7.0
  94. *
  95. * @param implementation
  96. * RPC interface implementation. Also used to deduce the type.
  97. */
  98. protected <T extends ServerRpc> void registerRpc(T implementation) {
  99. Class<?> cls = implementation.getClass();
  100. Class<?>[] interfaces = cls.getInterfaces();
  101. while (interfaces.length == 0) {
  102. // Search upwards until an interface is found. It must be found as T
  103. // extends ServerRpc
  104. cls = cls.getSuperclass();
  105. interfaces = cls.getInterfaces();
  106. }
  107. if (interfaces.length != 1
  108. || !(ServerRpc.class.isAssignableFrom(interfaces[0]))) {
  109. throw new RuntimeException(
  110. "Use registerRpc(T implementation, Class<T> rpcInterfaceType) if the Rpc implementation implements more than one interface");
  111. }
  112. @SuppressWarnings("unchecked")
  113. Class<T> type = (Class<T>) interfaces[0];
  114. registerRpc(implementation, type);
  115. }
  116. @Override
  117. public SharedState getState() {
  118. if (null == sharedState) {
  119. sharedState = createState();
  120. }
  121. return sharedState;
  122. }
  123. /**
  124. * Creates the shared state bean to be used in server to client
  125. * communication.
  126. * <p>
  127. * By default a state object of the defined return type of
  128. * {@link #getState()} is created. Subclasses can override this method and
  129. * return a new instance of the correct state class but this should rarely
  130. * be necessary.
  131. * </p>
  132. * <p>
  133. * No configuration of the values of the state should be performed in
  134. * {@link #createState()}.
  135. *
  136. * @since 7.0
  137. *
  138. * @return new shared state object
  139. */
  140. protected SharedState createState() {
  141. try {
  142. return getStateType().newInstance();
  143. } catch (Exception e) {
  144. throw new RuntimeException(
  145. "Error creating state of type " + getStateType().getName()
  146. + " for " + getClass().getName(), e);
  147. }
  148. }
  149. /*
  150. * (non-Javadoc)
  151. *
  152. * @see com.vaadin.terminal.gwt.server.ClientConnector#getStateType()
  153. */
  154. @Override
  155. public Class<? extends SharedState> getStateType() {
  156. try {
  157. Method m = getClass().getMethod("getState", (Class[]) null);
  158. Class<?> type = m.getReturnType();
  159. return type.asSubclass(SharedState.class);
  160. } catch (Exception e) {
  161. throw new RuntimeException("Error finding state type for "
  162. + getClass().getName(), e);
  163. }
  164. }
  165. /**
  166. * Returns an RPC proxy for a given server to client RPC interface for this
  167. * component.
  168. *
  169. * TODO more javadoc, subclasses, ...
  170. *
  171. * @param rpcInterface
  172. * RPC interface type
  173. *
  174. * @since 7.0
  175. */
  176. public <T extends ClientRpc> T getRpcProxy(final Class<T> rpcInterface) {
  177. // create, initialize and return a dynamic proxy for RPC
  178. try {
  179. if (!rpcProxyMap.containsKey(rpcInterface)) {
  180. Class<?> proxyClass = Proxy.getProxyClass(
  181. rpcInterface.getClassLoader(), rpcInterface);
  182. Constructor<?> constructor = proxyClass
  183. .getConstructor(InvocationHandler.class);
  184. T rpcProxy = rpcInterface.cast(constructor
  185. .newInstance(new RpcInvoicationHandler(rpcInterface)));
  186. // cache the proxy
  187. rpcProxyMap.put(rpcInterface, rpcProxy);
  188. }
  189. return (T) rpcProxyMap.get(rpcInterface);
  190. } catch (Exception e) {
  191. // TODO exception handling?
  192. throw new RuntimeException(e);
  193. }
  194. }
  195. private static final class AllChildrenIterable implements
  196. Iterable<ClientConnector>, Serializable {
  197. private final ClientConnector connector;
  198. private AllChildrenIterable(ClientConnector connector) {
  199. this.connector = connector;
  200. }
  201. @Override
  202. public Iterator<ClientConnector> iterator() {
  203. CombinedIterator<ClientConnector> iterator = new CombinedIterator<ClientConnector>();
  204. iterator.addIterator(connector.getExtensions().iterator());
  205. if (connector instanceof HasComponents) {
  206. HasComponents hasComponents = (HasComponents) connector;
  207. iterator.addIterator(hasComponents.iterator());
  208. }
  209. return iterator;
  210. }
  211. }
  212. private class RpcInvoicationHandler implements InvocationHandler,
  213. Serializable {
  214. private String rpcInterfaceName;
  215. public RpcInvoicationHandler(Class<?> rpcInterface) {
  216. rpcInterfaceName = rpcInterface.getName().replaceAll("\\$", ".");
  217. }
  218. @Override
  219. public Object invoke(Object proxy, Method method, Object[] args)
  220. throws Throwable {
  221. addMethodInvocationToQueue(rpcInterfaceName, method, args);
  222. // TODO no need to do full repaint if only RPC calls
  223. requestRepaint();
  224. return null;
  225. }
  226. }
  227. /**
  228. * For internal use: adds a method invocation to the pending RPC call queue.
  229. *
  230. * @param interfaceName
  231. * RPC interface name
  232. * @param method
  233. * RPC method
  234. * @param parameters
  235. * RPC all parameters
  236. *
  237. * @since 7.0
  238. */
  239. protected void addMethodInvocationToQueue(String interfaceName,
  240. Method method, Object[] parameters) {
  241. // add to queue
  242. pendingInvocations.add(new ClientMethodInvocation(this, interfaceName,
  243. method, parameters));
  244. }
  245. /**
  246. * @see RpcTarget#getRpcManager(Class)
  247. *
  248. * @param rpcInterface
  249. * RPC interface for which a call was made
  250. * @return RPC Manager handling calls for the interface
  251. *
  252. * @since 7.0
  253. */
  254. @Override
  255. public RpcManager getRpcManager(Class<?> rpcInterface) {
  256. return rpcManagerMap.get(rpcInterface);
  257. }
  258. @Override
  259. public List<ClientMethodInvocation> retrievePendingRpcCalls() {
  260. if (pendingInvocations.isEmpty()) {
  261. return Collections.emptyList();
  262. } else {
  263. List<ClientMethodInvocation> result = pendingInvocations;
  264. pendingInvocations = new ArrayList<ClientMethodInvocation>();
  265. return Collections.unmodifiableList(result);
  266. }
  267. }
  268. @Override
  269. public String getConnectorId() {
  270. if (connectorId == null) {
  271. if (getApplication() == null) {
  272. throw new RuntimeException(
  273. "Component must be attached to an application when getConnectorId() is called for the first time");
  274. }
  275. connectorId = getApplication().createConnectorId(this);
  276. }
  277. return connectorId;
  278. }
  279. /**
  280. * Finds the Application to which this connector belongs. If the connector
  281. * has not been attached, <code>null</code> is returned.
  282. *
  283. * @return The connector's application, or <code>null</code> if not attached
  284. */
  285. protected Application getApplication() {
  286. Root root = getRoot();
  287. if (root == null) {
  288. return null;
  289. } else {
  290. return root.getApplication();
  291. }
  292. }
  293. /**
  294. * Finds a Root ancestor of this connector. <code>null</code> is returned if
  295. * no Root ancestor is found (typically because the connector is not
  296. * attached to a proper hierarchy).
  297. *
  298. * @return the Root ancestor of this connector, or <code>null</code> if none
  299. * is found.
  300. */
  301. @Override
  302. public Root getRoot() {
  303. ClientConnector connector = this;
  304. while (connector != null) {
  305. if (connector instanceof Root) {
  306. return (Root) connector;
  307. }
  308. connector = connector.getParent();
  309. }
  310. return null;
  311. }
  312. private static Logger getLogger() {
  313. return Logger.getLogger(AbstractClientConnector.class.getName());
  314. }
  315. @Override
  316. public void requestRepaintAll() {
  317. requestRepaint();
  318. for (ClientConnector connector : getAllChildrenIterable(this)) {
  319. connector.requestRepaintAll();
  320. }
  321. }
  322. private static final class CombinedIterator<T> implements Iterator<T>,
  323. Serializable {
  324. private final Collection<Iterator<? extends T>> iterators = new ArrayList<Iterator<? extends T>>();
  325. public void addIterator(Iterator<? extends T> iterator) {
  326. iterators.add(iterator);
  327. }
  328. @Override
  329. public boolean hasNext() {
  330. for (Iterator<? extends T> i : iterators) {
  331. if (i.hasNext()) {
  332. return true;
  333. }
  334. }
  335. return false;
  336. }
  337. @Override
  338. public T next() {
  339. for (Iterator<? extends T> i : iterators) {
  340. if (i.hasNext()) {
  341. return i.next();
  342. }
  343. }
  344. throw new NoSuchElementException();
  345. }
  346. @Override
  347. public void remove() {
  348. throw new UnsupportedOperationException();
  349. }
  350. }
  351. /**
  352. * Get an Iterable for iterating over all child connectors, including both
  353. * extensions and child components.
  354. *
  355. * @param connector
  356. * the connector to get children for
  357. * @return an Iterable giving all child connectors.
  358. */
  359. public static Iterable<ClientConnector> getAllChildrenIterable(
  360. final ClientConnector connector) {
  361. return new AllChildrenIterable(connector);
  362. }
  363. @Override
  364. public Collection<Extension> getExtensions() {
  365. return Collections.unmodifiableCollection(extensions);
  366. }
  367. /**
  368. * Add an extension to this connector. This method is protected to allow
  369. * extensions to select which targets they can extend.
  370. *
  371. * @param extension
  372. * the extension to add
  373. */
  374. protected void addExtension(Extension extension) {
  375. ClientConnector previousParent = extension.getParent();
  376. if (previousParent == this) {
  377. // Nothing to do, already attached
  378. return;
  379. } else if (previousParent != null) {
  380. throw new IllegalStateException(
  381. "Moving an extension from one parent to another is not supported");
  382. }
  383. extensions.add(extension);
  384. extension.setParent(this);
  385. requestRepaint();
  386. }
  387. @Override
  388. public void removeExtension(Extension extension) {
  389. extension.setParent(null);
  390. extensions.remove(extension);
  391. requestRepaint();
  392. }
  393. @Override
  394. public void setParent(ClientConnector parent) {
  395. // If the parent is not changed, don't do anything
  396. if (parent == this.parent) {
  397. return;
  398. }
  399. if (parent != null && this.parent != null) {
  400. throw new IllegalStateException(getClass().getName()
  401. + " already has a parent.");
  402. }
  403. // Send detach event if the component have been connected to a window
  404. if (getApplication() != null) {
  405. detach();
  406. }
  407. // Connect to new parent
  408. this.parent = parent;
  409. // Send attach event if connected to an application
  410. if (getApplication() != null) {
  411. attach();
  412. }
  413. }
  414. @Override
  415. public ClientConnector getParent() {
  416. return parent;
  417. }
  418. @Override
  419. public void attach() {
  420. requestRepaint();
  421. getRoot().getConnectorTracker().registerConnector(this);
  422. for (ClientConnector connector : getAllChildrenIterable(this)) {
  423. connector.attach();
  424. }
  425. }
  426. /**
  427. * {@inheritDoc}
  428. *
  429. * <p>
  430. * The {@link #getApplication()} and {@link #getRoot()} methods might return
  431. * <code>null</code> after this method is called.
  432. * </p>
  433. */
  434. @Override
  435. public void detach() {
  436. for (ClientConnector connector : getAllChildrenIterable(this)) {
  437. connector.detach();
  438. }
  439. getRoot().getConnectorTracker().unregisterConnector(this);
  440. }
  441. @Override
  442. public boolean isConnectorEnabled() {
  443. if (getParent() == null) {
  444. // No parent -> the component cannot receive updates from the client
  445. return false;
  446. } else {
  447. return getParent().isConnectorEnabled();
  448. }
  449. }
  450. }