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

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