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 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.server;
  17. import java.io.IOException;
  18. import java.io.Serializable;
  19. import java.lang.reflect.Constructor;
  20. import java.lang.reflect.InvocationHandler;
  21. import java.lang.reflect.Method;
  22. import java.lang.reflect.Proxy;
  23. import java.util.ArrayList;
  24. import java.util.Collection;
  25. import java.util.Collections;
  26. import java.util.EventObject;
  27. import java.util.HashMap;
  28. import java.util.Iterator;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.NoSuchElementException;
  32. import java.util.WeakHashMap;
  33. import com.vaadin.event.EventRouter;
  34. import com.vaadin.event.MethodEventSource;
  35. import com.vaadin.shared.Registration;
  36. import com.vaadin.shared.communication.ClientRpc;
  37. import com.vaadin.shared.communication.ServerRpc;
  38. import com.vaadin.shared.communication.SharedState;
  39. import com.vaadin.shared.ui.ComponentStateUtil;
  40. import com.vaadin.ui.Component;
  41. import com.vaadin.ui.Component.Event;
  42. import com.vaadin.ui.HasComponents;
  43. import com.vaadin.ui.LegacyComponent;
  44. import com.vaadin.ui.UI;
  45. import com.vaadin.util.ReflectTools;
  46. import elemental.json.JsonObject;
  47. import elemental.json.JsonValue;
  48. /**
  49. * An abstract base class for ClientConnector implementations. This class
  50. * provides all the basic functionality required for connectors.
  51. *
  52. * @author Vaadin Ltd
  53. * @since 7.0.0
  54. */
  55. public abstract class AbstractClientConnector
  56. implements ClientConnector, MethodEventSource {
  57. /**
  58. * A map from client to server RPC interface class name to the RPC call
  59. * manager that handles incoming RPC calls for that interface.
  60. */
  61. private final Map<String, ServerRpcManager<?>> rpcManagerMap = new HashMap<>();
  62. /**
  63. * A map from server to client RPC interface class to the RPC proxy that
  64. * sends outgoing RPC calls for that interface.
  65. */
  66. private final Map<Class<?>, ClientRpc> rpcProxyMap = new HashMap<>();
  67. /**
  68. * Shared state object to be communicated from the server to the client when
  69. * modified.
  70. */
  71. private SharedState sharedState;
  72. private Class<? extends SharedState> stateType;
  73. /**
  74. * Pending RPC method invocations to be sent.
  75. */
  76. private List<ClientMethodInvocation> pendingInvocations = new ArrayList<>();
  77. private String connectorId;
  78. private final List<Extension> extensions = new ArrayList<>();
  79. /**
  80. * The EventRouter used for the event model.
  81. */
  82. private EventRouter eventRouter;
  83. private ErrorHandler errorHandler;
  84. /**
  85. * Static cache mapping AbstractClientConnector classes to their respective
  86. * ShareState classes. Using WeakHashMap since entries are recalculated on
  87. * demand.
  88. */
  89. private static final Map<Class<? extends AbstractClientConnector>, Class<? extends SharedState>> STATE_TYPE_CACHE = Collections
  90. .synchronizedMap(new WeakHashMap<>());
  91. @Override
  92. public Registration addAttachListener(AttachListener listener) {
  93. return addListener(AttachEvent.ATTACH_EVENT_IDENTIFIER,
  94. AttachEvent.class, listener, AttachListener.attachMethod);
  95. }
  96. @Override
  97. @Deprecated
  98. public void removeAttachListener(AttachListener listener) {
  99. removeListener(AttachEvent.ATTACH_EVENT_IDENTIFIER, AttachEvent.class,
  100. listener);
  101. }
  102. @Override
  103. public Registration addDetachListener(DetachListener listener) {
  104. return addListener(DetachEvent.DETACH_EVENT_IDENTIFIER,
  105. DetachEvent.class, listener, DetachListener.detachMethod);
  106. }
  107. @Override
  108. @Deprecated
  109. public void removeDetachListener(DetachListener listener) {
  110. removeListener(DetachEvent.DETACH_EVENT_IDENTIFIER, DetachEvent.class,
  111. listener);
  112. }
  113. /**
  114. * @deprecated As of 7.0, use {@link #markAsDirty()} instead. Note that you
  115. * typically do not need to call {@link #markAsDirty()} as
  116. * {@link #getState()} will mark the connector dirty and the
  117. * framework will then check what, if anything, needs to be sent
  118. * to the client. {@link LegacyComponent}s which rely on paint
  119. * might still need to call this or {@link #markAsDirty()} .
  120. */
  121. @Deprecated
  122. @Override
  123. public void requestRepaint() {
  124. markAsDirty();
  125. }
  126. /* Documentation copied from interface */
  127. @Override
  128. public void markAsDirty() {
  129. assert getSession() == null
  130. || getSession().hasLock() : buildLockAssertMessage(
  131. "markAsDirty()");
  132. UI uI = getUI();
  133. if (uI != null) {
  134. uI.getConnectorTracker().markDirty(this);
  135. }
  136. }
  137. private String buildLockAssertMessage(String method) {
  138. if (VaadinService.isOtherSessionLocked(getSession())) {
  139. return "The session of this connecor is not locked, but there is another session that is locked. "
  140. + "This might be caused by accidentally using a connector that belongs to another session.";
  141. } else {
  142. return "Session must be locked when " + method + " is called";
  143. }
  144. }
  145. /**
  146. * Registers an RPC interface implementation for this component.
  147. *
  148. * A component can listen to multiple RPC interfaces, and subclasses can
  149. * register additional implementations.
  150. *
  151. * @since 7.0
  152. *
  153. * @param implementation
  154. * RPC interface implementation
  155. * @param rpcInterfaceType
  156. * RPC interface class for which the implementation should be
  157. * registered
  158. */
  159. protected <T extends ServerRpc> void registerRpc(T implementation,
  160. Class<T> rpcInterfaceType) {
  161. rpcManagerMap.put(rpcInterfaceType.getName(),
  162. new ServerRpcManager<>(implementation, rpcInterfaceType));
  163. }
  164. /**
  165. * Registers an RPC interface implementation for this component.
  166. *
  167. * A component can listen to multiple RPC interfaces, and subclasses can
  168. * register additional implementations.
  169. *
  170. * @since 7.0
  171. *
  172. * @param implementation
  173. * RPC interface implementation. Also used to deduce the type.
  174. */
  175. protected <T extends ServerRpc> void registerRpc(T implementation) {
  176. // Search upwards until an interface is found. It must be found as T
  177. // extends ServerRpc
  178. Class<?> cls = implementation.getClass();
  179. Class<ServerRpc> serverRpcClass = getServerRpcInterface(cls);
  180. while (cls != null && serverRpcClass == null) {
  181. cls = cls.getSuperclass();
  182. serverRpcClass = getServerRpcInterface(cls);
  183. }
  184. if (serverRpcClass == null) {
  185. throw new RuntimeException(
  186. "No interface T extends ServerRpc found in the class hierarchy.");
  187. }
  188. registerRpc(implementation, serverRpcClass);
  189. }
  190. @SuppressWarnings("unchecked")
  191. private Class<ServerRpc> getServerRpcInterface(
  192. Class<?> implementationClass) {
  193. Class<ServerRpc> serverRpcClass = null;
  194. if (implementationClass != null) {
  195. for (Class<?> candidateInterface : implementationClass
  196. .getInterfaces()) {
  197. if (ServerRpc.class.isAssignableFrom(candidateInterface)) {
  198. if (serverRpcClass != null) {
  199. throw new RuntimeException(
  200. "Use registerRpc(T implementation, Class<T> rpcInterfaceType) if the Rpc implementation implements more than one interface");
  201. }
  202. serverRpcClass = (Class<ServerRpc>) candidateInterface;
  203. }
  204. }
  205. }
  206. return serverRpcClass;
  207. }
  208. /**
  209. * Returns the shared state for this connector. The shared state object is
  210. * shared between the server connector and the client connector. Changes are
  211. * only communicated from the server to the client and not in the other
  212. * direction.
  213. * <p>
  214. * As a side effect, marks the connector dirty so any changes done to the
  215. * state will be sent to the client. Use {@code getState(false)} to avoid
  216. * marking the connector as dirty.
  217. * </p>
  218. *
  219. * @return The shared state for this connector. Never null.
  220. */
  221. protected SharedState getState() {
  222. return getState(true);
  223. }
  224. /**
  225. * Returns the shared state for this connector.
  226. *
  227. * @param markAsDirty
  228. * true if the connector should automatically be marked dirty,
  229. * false otherwise
  230. *
  231. * @return The shared state for this connector. Never null.
  232. * @see #getState()
  233. */
  234. protected SharedState getState(boolean markAsDirty) {
  235. assert getSession() == null
  236. || getSession().hasLock() : buildLockAssertMessage(
  237. "getState()");
  238. if (null == sharedState) {
  239. sharedState = createState();
  240. }
  241. if (markAsDirty) {
  242. UI ui = getUI();
  243. if (ui != null && !ui.getConnectorTracker().isDirty(this)
  244. && !ui.getConnectorTracker().isWritingResponse()) {
  245. ui.getConnectorTracker().markDirty(this);
  246. }
  247. }
  248. return sharedState;
  249. }
  250. @Override
  251. public JsonObject encodeState() {
  252. return LegacyCommunicationManager.encodeState(this, getState(false));
  253. }
  254. /**
  255. * Creates the shared state bean to be used in server to client
  256. * communication.
  257. * <p>
  258. * By default a state object of the defined return type of
  259. * {@link #getState()} is created. Subclasses can override this method and
  260. * return a new instance of the correct state class but this should rarely
  261. * be necessary.
  262. * </p>
  263. * <p>
  264. * No configuration of the values of the state should be performed in
  265. * {@link #createState()}.
  266. *
  267. * @since 7.0
  268. *
  269. * @return new shared state object
  270. */
  271. protected SharedState createState() {
  272. try {
  273. return ReflectTools.createInstance(getStateType());
  274. } catch (Exception e) {
  275. throw new RuntimeException("Error creating state of type "
  276. + getStateType().getName() + " for " + getClass().getName(),
  277. e);
  278. }
  279. }
  280. @Override
  281. public Class<? extends SharedState> getStateType() {
  282. // Lazy load because finding type can be expensive because of the
  283. // exceptions flying around
  284. if (stateType == null) {
  285. // Cache because we don't need to do this once per instance
  286. stateType = STATE_TYPE_CACHE.computeIfAbsent(this.getClass(),
  287. key -> findStateType());
  288. }
  289. return stateType;
  290. }
  291. private Class<? extends SharedState> findStateType() {
  292. try {
  293. Class<?> class1 = getClass();
  294. while (class1 != null) {
  295. try {
  296. Method m = class1.getDeclaredMethod("getState",
  297. (Class[]) null);
  298. Class<?> type = m.getReturnType();
  299. if (!m.isSynthetic()) {
  300. return type.asSubclass(SharedState.class);
  301. }
  302. } catch (NoSuchMethodException nsme) {
  303. }
  304. // Try in superclass instead
  305. class1 = class1.getSuperclass();
  306. }
  307. throw new NoSuchMethodException(
  308. getClass().getCanonicalName() + ".getState()");
  309. } catch (Exception e) {
  310. throw new RuntimeException(
  311. "Error finding state type for " + getClass().getName(), e);
  312. }
  313. }
  314. /**
  315. * Returns an RPC proxy for a given server to client RPC interface for this
  316. * component.
  317. *
  318. * TODO more javadoc, subclasses, ...
  319. *
  320. * @param rpcInterface
  321. * RPC interface type
  322. *
  323. * @since 7.0
  324. */
  325. protected <T extends ClientRpc> T getRpcProxy(final Class<T> rpcInterface) {
  326. // create, initialize and return a dynamic proxy for RPC
  327. try {
  328. if (!rpcProxyMap.containsKey(rpcInterface)) {
  329. Class<?> proxyClass = Proxy.getProxyClass(
  330. rpcInterface.getClassLoader(), rpcInterface);
  331. Constructor<?> constructor = proxyClass
  332. .getConstructor(InvocationHandler.class);
  333. T rpcProxy = rpcInterface.cast(constructor
  334. .newInstance(new RpcInvocationHandler(rpcInterface)));
  335. // cache the proxy
  336. rpcProxyMap.put(rpcInterface, rpcProxy);
  337. }
  338. return (T) rpcProxyMap.get(rpcInterface);
  339. } catch (Exception e) {
  340. // TODO exception handling?
  341. throw new RuntimeException(e);
  342. }
  343. }
  344. private class RpcInvocationHandler
  345. implements InvocationHandler, Serializable {
  346. private final String rpcInterfaceName;
  347. public RpcInvocationHandler(Class<?> rpcInterface) {
  348. rpcInterfaceName = rpcInterface.getName().replaceAll("\\$", ".");
  349. }
  350. @Override
  351. public Object invoke(Object proxy, Method method, Object[] args)
  352. throws Throwable {
  353. if (method.getDeclaringClass() == Object.class) {
  354. // Don't add Object methods such as toString and hashCode as
  355. // invocations
  356. return method.invoke(this, args);
  357. }
  358. addMethodInvocationToQueue(rpcInterfaceName, method, args);
  359. return null;
  360. }
  361. }
  362. /**
  363. * For internal use: adds a method invocation to the pending RPC call queue.
  364. *
  365. * @param interfaceName
  366. * RPC interface name
  367. * @param method
  368. * RPC method
  369. * @param parameters
  370. * RPC all parameters
  371. *
  372. * @since 7.0
  373. */
  374. protected void addMethodInvocationToQueue(String interfaceName,
  375. Method method, Object[] parameters) {
  376. // add to queue
  377. pendingInvocations.add(new ClientMethodInvocation(this, interfaceName,
  378. method, parameters));
  379. // TODO no need to do full repaint if only RPC calls
  380. requestRepaint();
  381. }
  382. @Override
  383. public ServerRpcManager<?> getRpcManager(String rpcInterfaceName) {
  384. return rpcManagerMap.get(rpcInterfaceName);
  385. }
  386. @Override
  387. public List<ClientMethodInvocation> retrievePendingRpcCalls() {
  388. if (pendingInvocations.isEmpty()) {
  389. return Collections.emptyList();
  390. } else {
  391. List<ClientMethodInvocation> result = pendingInvocations;
  392. pendingInvocations = new ArrayList<>();
  393. return Collections.unmodifiableList(result);
  394. }
  395. }
  396. @Override
  397. public String getConnectorId() {
  398. if (connectorId == null) {
  399. if (getSession() == null) {
  400. throw new RuntimeException(
  401. "Component must be attached to a session when getConnectorId() is called for the first time");
  402. }
  403. connectorId = getSession().createConnectorId(this);
  404. }
  405. return connectorId;
  406. }
  407. /**
  408. * Finds the {@link VaadinSession} to which this connector belongs. If the
  409. * connector has not been attached, <code>null</code> is returned.
  410. *
  411. * @return The connector's session, or <code>null</code> if not attached
  412. */
  413. protected VaadinSession getSession() {
  414. UI uI = getUI();
  415. if (uI == null) {
  416. return null;
  417. } else {
  418. return uI.getSession();
  419. }
  420. }
  421. /**
  422. * Finds a UI ancestor of this connector. <code>null</code> is returned if
  423. * no UI ancestor is found (typically because the connector is not attached
  424. * to a proper hierarchy).
  425. *
  426. * @return the UI ancestor of this connector, or <code>null</code> if none
  427. * is found.
  428. */
  429. @Override
  430. public UI getUI() {
  431. ClientConnector connector = this;
  432. while (connector != null) {
  433. if (connector instanceof UI) {
  434. return (UI) connector;
  435. }
  436. connector = connector.getParent();
  437. }
  438. return null;
  439. }
  440. /**
  441. * @deprecated As of 7.0, use {@link #markAsDirtyRecursive()} instead
  442. */
  443. @Override
  444. @Deprecated
  445. public void requestRepaintAll() {
  446. markAsDirtyRecursive();
  447. }
  448. @Override
  449. public void markAsDirtyRecursive() {
  450. markAsDirty();
  451. for (ClientConnector connector : getAllChildrenIterable(this)) {
  452. connector.markAsDirtyRecursive();
  453. }
  454. }
  455. /**
  456. * Get an Iterable for iterating over all child connectors, including both
  457. * extensions and child components.
  458. *
  459. * @param connector
  460. * the connector to get children for
  461. * @return an Iterable giving all child connectors.
  462. */
  463. public static Iterable<? extends ClientConnector> getAllChildrenIterable(
  464. final ClientConnector connector) {
  465. Collection<Extension> extensions = connector.getExtensions();
  466. boolean hasComponents = connector instanceof HasComponents;
  467. boolean hasExtensions = !extensions.isEmpty();
  468. if (!hasComponents && !hasExtensions) {
  469. // If has neither component nor extensions, return immutable empty
  470. // list as iterable.
  471. return Collections.emptyList();
  472. }
  473. if (hasComponents && !hasExtensions) {
  474. // only components
  475. return (HasComponents) connector;
  476. }
  477. if (!hasComponents && hasExtensions) {
  478. // only extensions
  479. return extensions;
  480. }
  481. // combine the iterators of extensions and components to a new iterable.
  482. final Iterator<Component> componentsIterator = ((HasComponents) connector)
  483. .iterator();
  484. final Iterator<Extension> extensionsIterator = extensions.iterator();
  485. Iterable<? extends ClientConnector> combinedIterable = () -> new Iterator<ClientConnector>() {
  486. @Override
  487. public boolean hasNext() {
  488. return componentsIterator.hasNext()
  489. || extensionsIterator.hasNext();
  490. }
  491. @Override
  492. public ClientConnector next() {
  493. if (componentsIterator.hasNext()) {
  494. return componentsIterator.next();
  495. }
  496. if (extensionsIterator.hasNext()) {
  497. return extensionsIterator.next();
  498. }
  499. throw new NoSuchElementException();
  500. }
  501. @Override
  502. public void remove() {
  503. throw new UnsupportedOperationException();
  504. }
  505. };
  506. return combinedIterable;
  507. }
  508. @Override
  509. public Collection<Extension> getExtensions() {
  510. return Collections.unmodifiableCollection(extensions);
  511. }
  512. /**
  513. * Add an extension to this connector. This method is protected to allow
  514. * extensions to select which targets they can extend.
  515. *
  516. * @param extension
  517. * the extension to add
  518. */
  519. protected void addExtension(Extension extension) {
  520. ClientConnector previousParent = extension.getParent();
  521. if (equals(previousParent)) {
  522. // Nothing to do, already attached
  523. return;
  524. } else if (previousParent != null) {
  525. throw new IllegalStateException(
  526. "Moving an extension from one parent to another is not supported");
  527. }
  528. extensions.add(extension);
  529. extension.setParent(this);
  530. markAsDirty();
  531. }
  532. @Override
  533. public void removeExtension(Extension extension) {
  534. if (extension.getParent() != this) {
  535. throw new IllegalArgumentException(
  536. "This connector is not the parent for given extension");
  537. }
  538. extension.setParent(null);
  539. extensions.remove(extension);
  540. markAsDirty();
  541. }
  542. /*
  543. * (non-Javadoc)
  544. *
  545. * @see com.vaadin.server.ClientConnector#isAttached()
  546. */
  547. @Override
  548. public boolean isAttached() {
  549. return getSession() != null;
  550. }
  551. @Override
  552. public void attach() {
  553. markAsDirty();
  554. getUI().getConnectorTracker().registerConnector(this);
  555. for (ClientConnector connector : getAllChildrenIterable(this)) {
  556. connector.attach();
  557. }
  558. fireEvent(new AttachEvent(this));
  559. }
  560. /**
  561. * {@inheritDoc}
  562. *
  563. * <p>
  564. * The {@link #getSession()} and {@link #getUI()} methods might return
  565. * <code>null</code> after this method is called.
  566. * </p>
  567. */
  568. @Override
  569. public void detach() {
  570. for (ClientConnector connector : getAllChildrenIterable(this)) {
  571. connector.detach();
  572. }
  573. fireEvent(new DetachEvent(this));
  574. getUI().getConnectorTracker().unregisterConnector(this);
  575. }
  576. @Override
  577. public boolean isConnectorEnabled() {
  578. if (getParent() == null) {
  579. // No parent -> the component cannot receive updates from the client
  580. return false;
  581. } else {
  582. return getParent().isConnectorEnabled();
  583. }
  584. }
  585. @Override
  586. public void beforeClientResponse(boolean initial) {
  587. // Do nothing by default
  588. }
  589. @Override
  590. public boolean handleConnectorRequest(VaadinRequest request,
  591. VaadinResponse response, String path) throws IOException {
  592. DownloadStream stream = null;
  593. String[] parts = path.split("/", 2);
  594. String key = parts[0];
  595. VaadinSession session = getSession();
  596. session.lock();
  597. try {
  598. ConnectorResource resource = (ConnectorResource) getResource(key);
  599. if (resource == null) {
  600. return false;
  601. }
  602. stream = resource.getStream();
  603. } finally {
  604. session.unlock();
  605. }
  606. stream.writeResponse(request, response);
  607. return true;
  608. }
  609. /**
  610. * Gets a resource defined using {@link #setResource(String, Resource)} with
  611. * the corresponding key.
  612. *
  613. * @param key
  614. * the string identifier of the resource
  615. * @return a resource, or <code>null</code> if there's no resource
  616. * associated with the given key
  617. *
  618. * @see #setResource(String, Resource)
  619. */
  620. protected Resource getResource(String key) {
  621. return ResourceReference
  622. .getResource(getState(false).resources.get(key));
  623. }
  624. /**
  625. * Registers a resource with this connector using the given key. This will
  626. * make the URL for retrieving the resource available to the client-side
  627. * connector using
  628. * {@link com.vaadin.terminal.gwt.client.ui.AbstractConnector#getResourceUrl(String)}
  629. * with the same key.
  630. *
  631. * @param key
  632. * the string key to associate the resource with
  633. * @param resource
  634. * the resource to set, or <code>null</code> to clear a previous
  635. * association.
  636. */
  637. protected void setResource(String key, Resource resource) {
  638. ResourceReference resourceReference = ResourceReference.create(resource,
  639. this, key);
  640. if (resourceReference == null) {
  641. getState().resources.remove(key);
  642. } else {
  643. getState().resources.put(key, resourceReference);
  644. }
  645. }
  646. /* Listener code starts. Should be refactored. */
  647. /**
  648. * Registers a new listener with the specified activation method to listen
  649. * events generated by this component. If the activation method does not
  650. * have any arguments the event object will not be passed to it when it's
  651. * called.
  652. *
  653. * <p>
  654. * This method additionally informs the event-api to route events with the
  655. * given eventIdentifier to the components handleEvent function call.
  656. * </p>
  657. *
  658. * <p>
  659. * For more information on the inheritable event mechanism see the
  660. * {@link com.vaadin.event com.vaadin.event package documentation}.
  661. * </p>
  662. *
  663. * @param eventIdentifier
  664. * the identifier of the event to listen for
  665. * @param eventType
  666. * the type of the listened event. Events of this type or its
  667. * subclasses activate the listener.
  668. * @param target
  669. * the object instance who owns the activation method.
  670. * @param method
  671. * the activation method.
  672. * @return a registration object for removing the listener
  673. * @since 8.0
  674. */
  675. protected Registration addListener(String eventIdentifier,
  676. Class<?> eventType, Object target, Method method) {
  677. if (eventRouter == null) {
  678. eventRouter = new EventRouter();
  679. }
  680. return eventRouter.addListener(eventType, target, method,
  681. eventIdentifier, getState());
  682. }
  683. /**
  684. * Checks if the given {@link Event} type is listened for this component.
  685. *
  686. * @param eventType
  687. * the event type to be checked
  688. * @return true if a listener is registered for the given event type
  689. */
  690. protected boolean hasListeners(Class<?> eventType) {
  691. return eventRouter != null && eventRouter.hasListeners(eventType);
  692. }
  693. /**
  694. * Removes all registered listeners matching the given parameters. Since
  695. * this method receives the event type and the listener object as
  696. * parameters, it will unregister all <code>object</code>'s methods that are
  697. * registered to listen to events of type <code>eventType</code> generated
  698. * by this component.
  699. *
  700. * <p>
  701. * This method additionally informs the event-api to stop routing events
  702. * with the given {@code eventIdentifier} to the components handleEvent
  703. * function call.
  704. * </p>
  705. *
  706. * <p>
  707. * For more information on the inheritable event mechanism see the
  708. * {@link com.vaadin.event com.vaadin.event package documentation}.
  709. * </p>
  710. *
  711. * @param eventIdentifier
  712. * the identifier of the event to stop listening for
  713. * @param eventType
  714. * the exact event type the <code>object</code> listens to.
  715. * @param target
  716. * the target object that has registered to listen to events of
  717. * type <code>eventType</code> with one or more methods.
  718. *
  719. * @since 6.2
  720. * @deprecated use a {@link Registration} from
  721. * {@link #addListener(Class, Object, Method)} to remove a
  722. * listener
  723. */
  724. @Deprecated
  725. protected void removeListener(String eventIdentifier, Class<?> eventType,
  726. Object target) {
  727. if (eventRouter != null) {
  728. eventRouter.removeListener(eventType, target);
  729. if (!eventRouter.hasListeners(eventType)) {
  730. ComponentStateUtil.removeRegisteredEventListener(getState(),
  731. eventIdentifier);
  732. }
  733. }
  734. }
  735. /**
  736. * Registers a new listener with the specified activation method to listen
  737. * events generated by this component. If the activation method does not
  738. * have any arguments the event object will not be passed to it when it's
  739. * called.
  740. *
  741. * <p>
  742. * For more information on the inheritable event mechanism see the
  743. * {@link com.vaadin.event com.vaadin.event package documentation}.
  744. * </p>
  745. *
  746. * @param eventType
  747. * the type of the listened event. Events of this type or its
  748. * subclasses activate the listener.
  749. * @param target
  750. * the object instance who owns the activation method.
  751. * @param method
  752. * the activation method.
  753. * @return a registration object for removing the listener
  754. */
  755. @Override
  756. public Registration addListener(Class<?> eventType, Object target,
  757. Method method) {
  758. if (eventRouter == null) {
  759. eventRouter = new EventRouter();
  760. }
  761. return eventRouter.addListener(eventType, target, method);
  762. }
  763. /**
  764. * Convenience method for registering a new listener with the specified
  765. * activation method to listen events generated by this component. If the
  766. * activation method does not have any arguments the event object will not
  767. * be passed to it when it's called.
  768. *
  769. * <p>
  770. * This version of <code>addListener</code> gets the name of the activation
  771. * method as a parameter. The actual method is reflected from
  772. * <code>object</code>, and unless exactly one match is found,
  773. * <code>java.lang.IllegalArgumentException</code> is thrown.
  774. * </p>
  775. *
  776. * <p>
  777. * For more information on the inheritable event mechanism see the
  778. * {@link com.vaadin.event com.vaadin.event package documentation}.
  779. * </p>
  780. *
  781. * <p>
  782. * Note: Using this method is discouraged because it cannot be checked
  783. * during compilation. Use {@link #addListener(Class, Object, Method)} or
  784. * {@link #addListener(String, Class, Object, Method) instead. </p>
  785. *
  786. * @param eventType
  787. * the type of the listened event. Events of this type or its
  788. * subclasses activate the listener.
  789. * @param target
  790. * the object instance who owns the activation method.
  791. * @param methodName
  792. * the name of the activation method.
  793. * @return a registration object for removing the listener
  794. * @deprecated As of 7.0. This method should be avoided. Use
  795. * {@link #addListener(Class, Object, Method)} or
  796. * {@link #addListener(String, Class, Object, Method)} instead.
  797. * @since 8.0
  798. */
  799. @Override
  800. @Deprecated
  801. public Registration addListener(Class<?> eventType, Object target,
  802. String methodName) {
  803. if (eventRouter == null) {
  804. eventRouter = new EventRouter();
  805. }
  806. return eventRouter.addListener(eventType, target, methodName);
  807. }
  808. /**
  809. * Removes all registered listeners matching the given parameters. Since
  810. * this method receives the event type and the listener object as
  811. * parameters, it will unregister all <code>object</code>'s methods that are
  812. * registered to listen to events of type <code>eventType</code> generated
  813. * by this component.
  814. *
  815. * <p>
  816. * For more information on the inheritable event mechanism see the
  817. * {@link com.vaadin.event com.vaadin.event package documentation}.
  818. * </p>
  819. *
  820. * @param eventType
  821. * the exact event type the <code>object</code> listens to.
  822. * @param target
  823. * the target object that has registered to listen to events of
  824. * type <code>eventType</code> with one or more methods.
  825. * @deprecated use a {@link Registration} from {@link #addListener} to
  826. * remove a listener
  827. */
  828. @Deprecated
  829. @Override
  830. public void removeListener(Class<?> eventType, Object target) {
  831. if (eventRouter != null) {
  832. eventRouter.removeListener(eventType, target);
  833. }
  834. }
  835. /**
  836. * Removes one registered listener method. The given method owned by the
  837. * given object will no longer be called when the specified events are
  838. * generated by this component.
  839. *
  840. * <p>
  841. * For more information on the inheritable event mechanism see the
  842. * {@link com.vaadin.event com.vaadin.event package documentation}.
  843. * </p>
  844. *
  845. * @param eventType
  846. * the exact event type the <code>object</code> listens to.
  847. * @param target
  848. * target object that has registered to listen to events of type
  849. * <code>eventType</code> with one or more methods.
  850. * @param method
  851. * the method owned by <code>target</code> that's registered to
  852. * listen to events of type <code>eventType</code>.
  853. * @deprecated use a {@link Registration} from
  854. * {@link #addListener(Class, Object, Method)} to remove a
  855. * listener
  856. */
  857. @Override
  858. @Deprecated
  859. public void removeListener(Class<?> eventType, Object target,
  860. Method method) {
  861. if (eventRouter != null) {
  862. eventRouter.removeListener(eventType, target, method);
  863. }
  864. }
  865. /**
  866. * <p>
  867. * Removes one registered listener method. The given method owned by the
  868. * given object will no longer be called when the specified events are
  869. * generated by this component.
  870. * </p>
  871. *
  872. * <p>
  873. * This version of <code>removeListener</code> gets the name of the
  874. * activation method as a parameter. The actual method is reflected from
  875. * <code>target</code>, and unless exactly one match is found,
  876. * <code>java.lang.IllegalArgumentException</code> is thrown.
  877. * </p>
  878. *
  879. * <p>
  880. * For more information on the inheritable event mechanism see the
  881. * {@link com.vaadin.event com.vaadin.event package documentation}.
  882. * </p>
  883. *
  884. * @param eventType
  885. * the exact event type the <code>object</code> listens to.
  886. * @param target
  887. * the target object that has registered to listen to events of
  888. * type <code>eventType</code> with one or more methods.
  889. * @param methodName
  890. * the name of the method owned by <code>target</code> that's
  891. * registered to listen to events of type <code>eventType</code>.
  892. * @deprecated As of 7.0. This method should be avoided. Use
  893. * {@link #removeListener(Class, Object, Method)} instead.
  894. */
  895. @Deprecated
  896. @Override
  897. public void removeListener(Class<?> eventType, Object target,
  898. String methodName) {
  899. if (eventRouter != null) {
  900. eventRouter.removeListener(eventType, target, methodName);
  901. }
  902. }
  903. /**
  904. * Returns all listeners that are registered for the given event type or one
  905. * of its subclasses.
  906. *
  907. * @param eventType
  908. * The type of event to return listeners for.
  909. * @return A collection with all registered listeners. Empty if no listeners
  910. * are found.
  911. */
  912. public Collection<?> getListeners(Class<?> eventType) {
  913. if (eventRouter == null) {
  914. return Collections.emptyList();
  915. }
  916. return eventRouter.getListeners(eventType);
  917. }
  918. /**
  919. * Sends the event to all listeners.
  920. *
  921. * @param event
  922. * the Event to be sent to all listeners.
  923. */
  924. protected void fireEvent(EventObject event) {
  925. if (eventRouter != null) {
  926. eventRouter.fireEvent(event);
  927. }
  928. }
  929. /*
  930. * (non-Javadoc)
  931. *
  932. * @see com.vaadin.server.ClientConnector#getErrorHandler()
  933. */
  934. @Override
  935. public ErrorHandler getErrorHandler() {
  936. return errorHandler;
  937. }
  938. /*
  939. * (non-Javadoc)
  940. *
  941. * @see com.vaadin.server.ClientConnector#setErrorHandler(com.vaadin.server.
  942. * ErrorHandler)
  943. */
  944. @Override
  945. public void setErrorHandler(ErrorHandler errorHandler) {
  946. this.errorHandler = errorHandler;
  947. }
  948. /*
  949. * (non-Javadoc)
  950. *
  951. * @see java.lang.Object#equals(java.lang.Object)
  952. */
  953. @Override
  954. public boolean equals(Object obj) {
  955. if (this == obj) {
  956. return true;
  957. }
  958. /*
  959. * This equals method must return true when we're comparing an object to
  960. * its proxy. This happens a lot with CDI (and possibly Spring) when
  961. * we're injecting Components. See #14639
  962. */
  963. if (obj instanceof AbstractClientConnector) {
  964. AbstractClientConnector connector = (AbstractClientConnector) obj;
  965. return connector.isThis(this);
  966. }
  967. return false;
  968. }
  969. /**
  970. * For internal use only, may be changed or removed in future versions.
  971. * <p>
  972. * This method must be protected, because otherwise it will not be redefined
  973. * by the proxy to actually be called on the underlying instance.
  974. * <p>
  975. * See #14639
  976. *
  977. * @deprecated only defined for framework hacks, do not use.
  978. */
  979. @Deprecated
  980. protected boolean isThis(Object that) {
  981. return this == that;
  982. }
  983. /*
  984. * (non-Javadoc)
  985. *
  986. * @see java.lang.Object#hashCode()
  987. */
  988. @Override
  989. public int hashCode() {
  990. return super.hashCode();
  991. }
  992. /**
  993. * Sets the expected value of a state property so that changes can be
  994. * properly sent to the client. This needs to be done in cases where a state
  995. * change originates from the client, since otherwise the server-side would
  996. * fail to recognize if the value is changed back to its previous value.
  997. *
  998. * @param propertyName
  999. * the name of the shared state property to update
  1000. * @param newValue
  1001. * the new diffstate reference value
  1002. */
  1003. protected void updateDiffstate(String propertyName, JsonValue newValue) {
  1004. if (!isAttached()) {
  1005. return;
  1006. }
  1007. JsonObject diffState = getUI().getConnectorTracker().getDiffState(this);
  1008. if (diffState == null) {
  1009. return;
  1010. }
  1011. assert diffState.hasKey(propertyName) : "Diffstate for "
  1012. + getClass().getName() + " has no property named "
  1013. + propertyName;
  1014. diffState.put(propertyName, newValue);
  1015. }
  1016. }