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.

ServerRpcHandler.java 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. /*
  2. * Copyright 2000-2021 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.communication;
  17. import java.io.IOException;
  18. import java.io.Reader;
  19. import java.io.Serializable;
  20. import java.lang.reflect.Type;
  21. import java.util.ArrayList;
  22. import java.util.HashSet;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import java.util.logging.Level;
  27. import java.util.logging.Logger;
  28. import com.vaadin.server.ClientConnector;
  29. import com.vaadin.server.Constants;
  30. import com.vaadin.server.JsonCodec;
  31. import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException;
  32. import com.vaadin.server.ServerRpcManager;
  33. import com.vaadin.server.ServerRpcManager.RpcInvocationException;
  34. import com.vaadin.server.ServerRpcMethodInvocation;
  35. import com.vaadin.server.VaadinRequest;
  36. import com.vaadin.server.VaadinService;
  37. import com.vaadin.server.VariableOwner;
  38. import com.vaadin.shared.ApplicationConstants;
  39. import com.vaadin.shared.Connector;
  40. import com.vaadin.shared.Version;
  41. import com.vaadin.shared.communication.LegacyChangeVariablesInvocation;
  42. import com.vaadin.shared.communication.MethodInvocation;
  43. import com.vaadin.shared.communication.ServerRpc;
  44. import com.vaadin.shared.communication.UidlValue;
  45. import com.vaadin.shared.data.DataRequestRpc;
  46. import com.vaadin.ui.Component;
  47. import com.vaadin.ui.ConnectorTracker;
  48. import com.vaadin.ui.UI;
  49. import elemental.json.JsonArray;
  50. import elemental.json.JsonException;
  51. import elemental.json.JsonObject;
  52. import elemental.json.JsonValue;
  53. import elemental.json.impl.JsonUtil;
  54. /**
  55. * Handles a client-to-server message containing serialized {@link ServerRpc
  56. * server RPC} invocations.
  57. *
  58. * @author Vaadin Ltd
  59. * @since 7.1
  60. */
  61. @SuppressWarnings("deprecation")
  62. public class ServerRpcHandler implements Serializable {
  63. /**
  64. * A data transfer object representing an RPC request sent by the client
  65. * side.
  66. *
  67. * @since 7.2
  68. * @author Vaadin Ltd
  69. */
  70. public static class RpcRequest implements Serializable {
  71. private final String csrfToken;
  72. private final JsonArray invocations;
  73. private final int syncId;
  74. private final JsonObject json;
  75. private final boolean resynchronize;
  76. private final int clientToServerMessageId;
  77. private String widgetsetVersion = null;
  78. public RpcRequest(String jsonString, VaadinRequest request) {
  79. json = JsonUtil.parse(jsonString);
  80. JsonValue token = json.get(ApplicationConstants.CSRF_TOKEN);
  81. if (token == null) {
  82. csrfToken = ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE;
  83. } else {
  84. String csrfToken = token.asString();
  85. if (csrfToken.isEmpty()) {
  86. csrfToken = ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE;
  87. }
  88. this.csrfToken = csrfToken;
  89. }
  90. if (request.getService().getDeploymentConfiguration()
  91. .isSyncIdCheckEnabled()) {
  92. syncId = (int) json
  93. .getNumber(ApplicationConstants.SERVER_SYNC_ID);
  94. } else {
  95. syncId = -1;
  96. }
  97. if (json.hasKey(ApplicationConstants.RESYNCHRONIZE_ID)) {
  98. resynchronize = json
  99. .getBoolean(ApplicationConstants.RESYNCHRONIZE_ID);
  100. } else {
  101. resynchronize = false;
  102. }
  103. if (json.hasKey(ApplicationConstants.WIDGETSET_VERSION_ID)) {
  104. widgetsetVersion = json
  105. .getString(ApplicationConstants.WIDGETSET_VERSION_ID);
  106. }
  107. if (json.hasKey(ApplicationConstants.CLIENT_TO_SERVER_ID)) {
  108. clientToServerMessageId = (int) json
  109. .getNumber(ApplicationConstants.CLIENT_TO_SERVER_ID);
  110. } else {
  111. getLogger()
  112. .warning("Server message without client id received");
  113. clientToServerMessageId = -1;
  114. }
  115. invocations = json.getArray(ApplicationConstants.RPC_INVOCATIONS);
  116. }
  117. /**
  118. * Gets the CSRF security token (double submit cookie) for this request.
  119. *
  120. * @return the CSRF security token for this current change request
  121. */
  122. public String getCsrfToken() {
  123. return csrfToken;
  124. }
  125. /**
  126. * Gets the data to recreate the RPC as requested by the client side.
  127. *
  128. * @return the data describing which RPC should be made, and all their
  129. * data
  130. */
  131. public JsonArray getRpcInvocationsData() {
  132. return invocations;
  133. }
  134. /**
  135. * Gets the sync id last seen by the client.
  136. *
  137. * @return the last sync id given by the server, according to the
  138. * client's request
  139. */
  140. public int getSyncId() {
  141. return syncId;
  142. }
  143. /**
  144. * Checks if this is a request to resynchronize the client side.
  145. *
  146. * @return true if this is a resynchronization request, false otherwise
  147. */
  148. public boolean isResynchronize() {
  149. return resynchronize;
  150. }
  151. /**
  152. * Gets the id of the client to server message.
  153. *
  154. * @since 7.6
  155. * @return the server message id
  156. */
  157. public int getClientToServerId() {
  158. return clientToServerMessageId;
  159. }
  160. /**
  161. * Gets the entire request in JSON format, as it was received from the
  162. * client.
  163. * <p>
  164. * <em>Note:</em> This is a shared reference - any modifications made
  165. * will be shared.
  166. *
  167. * @return the raw JSON object that was received from the client
  168. *
  169. */
  170. public JsonObject getRawJson() {
  171. return json;
  172. }
  173. /**
  174. * Gets the widget set version reported by the client.
  175. *
  176. * @since 7.6
  177. * @return The widget set version reported by the client or null if the
  178. * message did not contain a widget set version
  179. */
  180. public String getWidgetsetVersion() {
  181. return widgetsetVersion;
  182. }
  183. }
  184. private static final int MAX_BUFFER_SIZE = 64 * 1024;
  185. /**
  186. * Reads JSON containing zero or more serialized RPC calls (including legacy
  187. * variable changes) and executes the calls.
  188. *
  189. * @param ui
  190. * The {@link UI} receiving the calls. Cannot be null.
  191. * @param reader
  192. * The {@link Reader} used to read the JSON.
  193. * @param request
  194. * The {@link VaadinRequest} to handle.
  195. * @throws IOException
  196. * If reading the message fails.
  197. * @throws InvalidUIDLSecurityKeyException
  198. * If the received security key does not match the one stored in
  199. * the session.
  200. */
  201. public void handleRpc(UI ui, Reader reader, VaadinRequest request)
  202. throws IOException, InvalidUIDLSecurityKeyException {
  203. ui.getSession().setLastRequestTimestamp(System.currentTimeMillis());
  204. String changeMessage = getMessage(reader);
  205. if (changeMessage == null || changeMessage.isEmpty()) {
  206. // The client sometimes sends empty messages, this is probably a bug
  207. return;
  208. }
  209. RpcRequest rpcRequest = new RpcRequest(changeMessage, request);
  210. // Security: double cookie submission pattern unless disabled by
  211. // property
  212. if (!VaadinService.isCsrfTokenValid(ui.getSession(),
  213. rpcRequest.getCsrfToken())) {
  214. throw new InvalidUIDLSecurityKeyException("");
  215. }
  216. checkWidgetsetVersion(rpcRequest.getWidgetsetVersion());
  217. int expectedId = ui.getLastProcessedClientToServerId() + 1;
  218. if (rpcRequest.getClientToServerId() != -1
  219. && rpcRequest.getClientToServerId() != expectedId) {
  220. // Invalid message id, skip RPC processing but force a full
  221. // re-synchronization of the client as it might have not received
  222. // the previous response (e.g. due to a bad connection)
  223. // Must resync also for duplicate messages because the server might
  224. // have generated a response for the first message but the response
  225. // did not reach the client. When the client re-sends the message,
  226. // it would only get an empty response (because the dirty flags have
  227. // been cleared on the server) and would be out of sync
  228. ui.getSession().getCommunicationManager().repaintAll(ui);
  229. if (rpcRequest.getClientToServerId() < expectedId) {
  230. // Just a duplicate message due to a bad connection or similar
  231. // It has already been handled by the server so it is safe to
  232. // ignore
  233. getLogger()
  234. .fine("Ignoring old message from the client. Expected: "
  235. + expectedId + ", got: "
  236. + rpcRequest.getClientToServerId());
  237. } else {
  238. getLogger().warning(
  239. "Unexpected message id from the client. Expected: "
  240. + expectedId + ", got: "
  241. + rpcRequest.getClientToServerId());
  242. }
  243. } else {
  244. // Message id ok, process RPCs
  245. ui.setLastProcessedClientToServerId(expectedId);
  246. handleInvocations(ui, rpcRequest.getSyncId(),
  247. rpcRequest.getRpcInvocationsData());
  248. }
  249. if (rpcRequest.isResynchronize()) {
  250. ui.getSession().getCommunicationManager().repaintAll(ui);
  251. }
  252. }
  253. /**
  254. * Checks that the version reported by the client (widgetset) matches that
  255. * of the server.
  256. *
  257. * @param widgetsetVersion
  258. * the widget set version reported by the client or null
  259. */
  260. private void checkWidgetsetVersion(String widgetsetVersion) {
  261. if (widgetsetVersion == null) {
  262. // Only check when the widgetset version is reported. It is reported
  263. // in the first UIDL request (not the initial request as it is a
  264. // plain GET /)
  265. return;
  266. }
  267. if (!Version.getFullVersion().equals(widgetsetVersion)) {
  268. getLogger().warning(String.format(Constants.WIDGETSET_MISMATCH_INFO,
  269. Version.getFullVersion(), widgetsetVersion));
  270. }
  271. }
  272. /**
  273. * Processes invocations data received from the client.
  274. * <p>
  275. * The invocations data can contain any number of RPC calls, including
  276. * legacy variable change calls that are processed separately.
  277. * <p>
  278. * Consecutive changes to the value of the same variable are combined and
  279. * changeVariables() is only called once for them. This preserves the Vaadin
  280. * 6 semantics for components and add-ons that do not use Vaadin 7 RPC
  281. * directly.
  282. *
  283. * @param ui
  284. * the UI receiving the invocations data
  285. * @param lastSyncIdSeenByClient
  286. * the most recent sync id the client has seen at the time the
  287. * request was sent
  288. * @param invocationsData
  289. * JSON containing all information needed to execute all
  290. * requested RPC calls.
  291. * @since 7.7
  292. */
  293. protected void handleInvocations(UI ui, int lastSyncIdSeenByClient,
  294. JsonArray invocationsData) {
  295. try {
  296. ConnectorTracker connectorTracker = ui.getConnectorTracker();
  297. Set<Connector> enabledConnectors = new HashSet<>();
  298. List<MethodInvocation> invocations = parseInvocations(
  299. ui.getConnectorTracker(), invocationsData);
  300. for (MethodInvocation invocation : invocations) {
  301. final ClientConnector connector = connectorTracker
  302. .getConnector(invocation.getConnectorId());
  303. if (connector != null && connector.isConnectorEnabled()) {
  304. enabledConnectors.add(connector);
  305. }
  306. }
  307. for (MethodInvocation invocation : invocations) {
  308. final ClientConnector connector = connectorTracker
  309. .getConnector(invocation.getConnectorId());
  310. if (connector == null) {
  311. logUnknownConnector(invocation.getConnectorId(),
  312. invocation.getInterfaceName(),
  313. invocation.getMethodName());
  314. continue;
  315. }
  316. if (!enabledConnectors.contains(connector)) {
  317. if (invocation instanceof LegacyChangeVariablesInvocation) {
  318. LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
  319. // TODO convert window close to a separate RPC call and
  320. // handle above - not a variable change
  321. // Handle special case where window-close is called
  322. // after the window has been removed from the
  323. // application or the application has closed
  324. Map<String, Object> changes = legacyInvocation
  325. .getVariableChanges();
  326. if (changes.size() == 1 && changes.containsKey("close")
  327. && Boolean.TRUE.equals(changes.get("close"))) {
  328. // Silently ignore this
  329. continue;
  330. }
  331. } else if (invocation instanceof ServerRpcMethodInvocation) {
  332. ServerRpcMethodInvocation rpc = (ServerRpcMethodInvocation) invocation;
  333. // special case for data communicator requesting more
  334. // data
  335. if (DataRequestRpc.class.getName()
  336. .equals(rpc.getInterfaceClass().getName())) {
  337. handleInvocation(ui, connector, rpc);
  338. }
  339. continue;
  340. }
  341. // Connector is disabled, log a warning and move to the next
  342. getLogger().warning(
  343. getIgnoredDisabledError("RPC call", connector));
  344. continue;
  345. }
  346. // DragAndDropService has null UI
  347. if (connector.getUI() != null
  348. && connector.getUI().isClosing()) {
  349. String msg = "Ignoring RPC call for connector "
  350. + connector.getClass().getName();
  351. if (connector instanceof Component) {
  352. String caption = ((Component) connector).getCaption();
  353. if (caption != null) {
  354. msg += ", caption=" + caption;
  355. }
  356. }
  357. msg += " in closed UI";
  358. getLogger().warning(msg);
  359. continue;
  360. }
  361. if (invocation instanceof ServerRpcMethodInvocation) {
  362. handleInvocation(ui, connector,
  363. (ServerRpcMethodInvocation) invocation);
  364. } else {
  365. LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
  366. handleInvocation(ui, connector, legacyInvocation);
  367. }
  368. }
  369. } catch (JsonException e) {
  370. getLogger().warning("Unable to parse RPC call from the client: "
  371. + e.getMessage());
  372. throw new RuntimeException(e);
  373. }
  374. }
  375. private void logUnknownConnector(String connectorId, String interfaceName,
  376. String methodName) {
  377. getLogger().log(Level.FINE,
  378. "Received RPC call for unknown connector with id {0} (tried to invoke {1}.{2})",
  379. new Object[] { connectorId, interfaceName, methodName });
  380. }
  381. /**
  382. * Handles the given RPC method invocation for the given connector.
  383. *
  384. * @since 7.7
  385. * @param ui
  386. * the UI containing the connector
  387. * @param connector
  388. * the connector the RPC is targeted to
  389. * @param invocation
  390. * information about the rpc to invoke
  391. */
  392. protected void handleInvocation(UI ui, ClientConnector connector,
  393. ServerRpcMethodInvocation invocation) {
  394. try {
  395. ServerRpcManager.applyInvocation(connector, invocation);
  396. } catch (RpcInvocationException e) {
  397. ui.getSession().getCommunicationManager()
  398. .handleConnectorRelatedException(connector, e);
  399. }
  400. }
  401. /**
  402. * Handles the given Legacy variable change RPC method invocation for the
  403. * given connector.
  404. *
  405. * @since 7.7
  406. * @param ui
  407. * the UI containing the connector
  408. * @param connector
  409. * the connector the RPC is targeted to
  410. * @param legacyInvocation
  411. * information about the rpc to invoke
  412. */
  413. protected void handleInvocation(UI ui, ClientConnector connector,
  414. LegacyChangeVariablesInvocation legacyInvocation) {
  415. Map<String, Object> changes = legacyInvocation.getVariableChanges();
  416. try {
  417. if (connector instanceof VariableOwner) {
  418. // The source parameter is never used anywhere
  419. changeVariables(null, (VariableOwner) connector, changes);
  420. } else {
  421. throw new IllegalStateException(
  422. "Received a legacy variable change for "
  423. + connector.getClass().getName() + " ("
  424. + connector.getConnectorId()
  425. + ") which is not a VariableOwner. The client-side connector sent these legacy variables: "
  426. + changes.keySet());
  427. }
  428. } catch (Exception e) {
  429. ui.getSession().getCommunicationManager()
  430. .handleConnectorRelatedException(connector, e);
  431. }
  432. }
  433. /**
  434. * Parse JSON from the client into a list of MethodInvocation instances.
  435. *
  436. * @param connectorTracker
  437. * The ConnectorTracker used to lookup connectors
  438. * @param invocationsJson
  439. * JSON containing all information needed to execute all
  440. * requested RPC calls.
  441. * @return list of MethodInvocation to perform
  442. */
  443. private List<MethodInvocation> parseInvocations(
  444. ConnectorTracker connectorTracker, JsonArray invocationsJson) {
  445. int invocationCount = invocationsJson.length();
  446. List<MethodInvocation> invocations = new ArrayList<>(invocationCount);
  447. MethodInvocation previousInvocation = null;
  448. // parse JSON to MethodInvocations
  449. for (int i = 0; i < invocationCount; ++i) {
  450. JsonArray invocationJson = invocationsJson.getArray(i);
  451. MethodInvocation invocation = parseInvocation(invocationJson,
  452. previousInvocation, connectorTracker);
  453. if (invocation != null) {
  454. // Can be null if the invocation was a legacy invocation and it
  455. // was merged with the previous one or if the invocation was
  456. // rejected because of an error.
  457. invocations.add(invocation);
  458. previousInvocation = invocation;
  459. }
  460. }
  461. return invocations;
  462. }
  463. private MethodInvocation parseInvocation(JsonArray invocationJson,
  464. MethodInvocation previousInvocation,
  465. ConnectorTracker connectorTracker) {
  466. String connectorId = invocationJson.getString(0);
  467. String interfaceName = invocationJson.getString(1);
  468. String methodName = invocationJson.getString(2);
  469. JsonArray parametersJson = invocationJson.getArray(3);
  470. if (LegacyChangeVariablesInvocation
  471. .isLegacyVariableChange(interfaceName, methodName)) {
  472. if (!(previousInvocation instanceof LegacyChangeVariablesInvocation)) {
  473. previousInvocation = null;
  474. }
  475. return parseLegacyChangeVariablesInvocation(connectorId,
  476. (LegacyChangeVariablesInvocation) previousInvocation,
  477. parametersJson, connectorTracker);
  478. } else {
  479. return parseServerRpcInvocation(connectorId, interfaceName,
  480. methodName, parametersJson, connectorTracker);
  481. }
  482. }
  483. private LegacyChangeVariablesInvocation parseLegacyChangeVariablesInvocation(
  484. String connectorId,
  485. LegacyChangeVariablesInvocation previousInvocation,
  486. JsonArray parametersJson, ConnectorTracker connectorTracker) {
  487. if (parametersJson.length() != 2) {
  488. throw new JsonException(
  489. "Invalid parameters in legacy change variables call. Expected 2, was "
  490. + parametersJson.length());
  491. }
  492. String variableName = parametersJson.getString(0);
  493. UidlValue uidlValue = (UidlValue) JsonCodec.decodeInternalType(
  494. UidlValue.class, true, parametersJson.get(1), connectorTracker);
  495. Object value = uidlValue.getValue();
  496. if (previousInvocation != null
  497. && previousInvocation.getConnectorId().equals(connectorId)) {
  498. previousInvocation.setVariableChange(variableName, value);
  499. return null;
  500. } else {
  501. return new LegacyChangeVariablesInvocation(connectorId,
  502. variableName, value);
  503. }
  504. }
  505. private ServerRpcMethodInvocation parseServerRpcInvocation(
  506. String connectorId, String interfaceName, String methodName,
  507. JsonArray parametersJson, ConnectorTracker connectorTracker)
  508. throws JsonException {
  509. ClientConnector connector = connectorTracker.getConnector(connectorId);
  510. if (connector == null) {
  511. logUnknownConnector(connectorId, interfaceName, methodName);
  512. return null;
  513. }
  514. ServerRpcManager<?> rpcManager = connector.getRpcManager(interfaceName);
  515. if (rpcManager == null) {
  516. /*
  517. * Security: Don't even decode the json parameters if no RpcManager
  518. * corresponding to the received method invocation has been
  519. * registered.
  520. */
  521. String message = "Ignoring RPC call to " + interfaceName + "."
  522. + methodName + " in connector "
  523. + connector.getClass().getName() + "(" + connectorId
  524. + ") as no RPC implementation is registered";
  525. assert rpcManager != null : message;
  526. getLogger().warning(message);
  527. return null;
  528. }
  529. // Use interface from RpcManager instead of loading the class based on
  530. // the string name to avoid problems with OSGi
  531. Class<? extends ServerRpc> rpcInterface = rpcManager.getRpcInterface();
  532. ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation(
  533. connectorId, rpcInterface, methodName, parametersJson.length());
  534. Object[] parameters = new Object[parametersJson.length()];
  535. Type[] declaredRpcMethodParameterTypes = invocation.getMethod()
  536. .getGenericParameterTypes();
  537. for (int j = 0; j < parametersJson.length(); ++j) {
  538. JsonValue parameterValue = parametersJson.get(j);
  539. Type parameterType = declaredRpcMethodParameterTypes[j];
  540. parameters[j] = JsonCodec.decodeInternalOrCustomType(parameterType,
  541. parameterValue, connectorTracker);
  542. }
  543. invocation.setParameters(parameters);
  544. return invocation;
  545. }
  546. protected void changeVariables(Object source, VariableOwner owner,
  547. Map<String, Object> m) {
  548. owner.changeVariables(source, m);
  549. }
  550. protected String getMessage(Reader reader) throws IOException {
  551. StringBuilder sb = new StringBuilder(MAX_BUFFER_SIZE);
  552. char[] buffer = new char[MAX_BUFFER_SIZE];
  553. while (true) {
  554. int read = reader.read(buffer);
  555. if (read == -1) {
  556. break;
  557. }
  558. sb.append(buffer, 0, read);
  559. }
  560. return sb.toString();
  561. }
  562. private static final Logger getLogger() {
  563. return Logger.getLogger(ServerRpcHandler.class.getName());
  564. }
  565. /**
  566. * Generates an error message when the client is trying to do something
  567. * ('what') with a connector which is disabled or invisible.
  568. *
  569. * @since 7.1.8
  570. * @param what
  571. * the ignored operation
  572. * @param connector
  573. * the connector which is disabled (or invisible)
  574. * @return an error message
  575. */
  576. public static String getIgnoredDisabledError(String what,
  577. ClientConnector connector) {
  578. String msg = "Ignoring " + what + " for disabled connector "
  579. + connector.getClass().getName();
  580. if (connector instanceof Component) {
  581. String caption = ((Component) connector).getCaption();
  582. if (caption != null) {
  583. msg += ", caption=" + caption;
  584. }
  585. }
  586. return msg;
  587. }
  588. }