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.

XmppParser.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. /*
  2. * Copyright (c) 2006-2015 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.parser.xmpp;
  23. import com.dmdirc.parser.common.AwayState;
  24. import com.dmdirc.parser.common.BaseSocketAwareParser;
  25. import com.dmdirc.parser.common.ChannelJoinRequest;
  26. import com.dmdirc.parser.common.ChildImplementations;
  27. import com.dmdirc.parser.common.CompositionState;
  28. import com.dmdirc.parser.common.DefaultStringConverter;
  29. import com.dmdirc.parser.common.ParserError;
  30. import com.dmdirc.parser.common.QueuePriority;
  31. import com.dmdirc.parser.events.AwayStateEvent;
  32. import com.dmdirc.parser.events.ChannelSelfJoinEvent;
  33. import com.dmdirc.parser.events.CompositionStateChangeEvent;
  34. import com.dmdirc.parser.events.ConnectErrorEvent;
  35. import com.dmdirc.parser.events.DataInEvent;
  36. import com.dmdirc.parser.events.DataOutEvent;
  37. import com.dmdirc.parser.events.NumericEvent;
  38. import com.dmdirc.parser.events.OtherAwayStateEvent;
  39. import com.dmdirc.parser.events.PrivateActionEvent;
  40. import com.dmdirc.parser.events.PrivateMessageEvent;
  41. import com.dmdirc.parser.events.ServerReadyEvent;
  42. import com.dmdirc.parser.events.SocketCloseEvent;
  43. import com.dmdirc.parser.interfaces.ChannelInfo;
  44. import com.dmdirc.parser.interfaces.ClientInfo;
  45. import com.dmdirc.parser.interfaces.LocalClientInfo;
  46. import com.dmdirc.parser.interfaces.StringConverter;
  47. import com.dmdirc.parser.interfaces.callbacks.CallbackInterface;
  48. import com.dmdirc.parser.interfaces.callbacks.DataInListener;
  49. import com.dmdirc.parser.interfaces.callbacks.DataOutListener;
  50. import java.net.URI;
  51. import java.util.Collection;
  52. import java.util.Collections;
  53. import java.util.Date;
  54. import java.util.HashMap;
  55. import java.util.List;
  56. import java.util.Map;
  57. import java.util.regex.Matcher;
  58. import java.util.regex.Pattern;
  59. import org.jivesoftware.smack.Chat;
  60. import org.jivesoftware.smack.ChatManagerListener;
  61. import org.jivesoftware.smack.ConnectionConfiguration;
  62. import org.jivesoftware.smack.ConnectionListener;
  63. import org.jivesoftware.smack.PacketListener;
  64. import org.jivesoftware.smack.RosterEntry;
  65. import org.jivesoftware.smack.RosterListener;
  66. import org.jivesoftware.smack.XMPPConnection;
  67. import org.jivesoftware.smack.XMPPException;
  68. import org.jivesoftware.smack.filter.PacketFilter;
  69. import org.jivesoftware.smack.packet.Message;
  70. import org.jivesoftware.smack.packet.Packet;
  71. import org.jivesoftware.smack.packet.Presence;
  72. import org.jivesoftware.smackx.ChatState;
  73. import org.jivesoftware.smackx.ChatStateListener;
  74. import org.jivesoftware.smackx.ChatStateManager;
  75. import org.jivesoftware.smackx.muc.MultiUserChat;
  76. import org.slf4j.LoggerFactory;
  77. /**
  78. * A parser which can understand the XMPP protocol.
  79. */
  80. @ChildImplementations({
  81. XmppClientInfo.class, XmppLocalClientInfo.class, XmppFakeChannel.class,
  82. XmppChannelClientInfo.class
  83. })
  84. public class XmppParser extends BaseSocketAwareParser {
  85. private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(XmppParser.class);
  86. /** Pattern to use to extract priority. */
  87. private static final Pattern PRIORITY_PATTERN = Pattern.compile(
  88. "(?i)(?:^|&)priority=([0-9]+)(?:$|&)");
  89. /** The connection to use. */
  90. private XMPPConnection connection;
  91. /** The state manager for the current connection. */
  92. private ChatStateManager stateManager;
  93. /** A cache of known chats. */
  94. private final Map<String, Chat> chats = new HashMap<>();
  95. /** A cache of known clients. */
  96. private final Map<String, XmppClientInfo> contacts = new HashMap<>();
  97. /** Whether or not to use a fake local channel for a buddy list replacement. */
  98. private final boolean useFakeChannel;
  99. /** The priority of this endpoint. */
  100. private final int priority;
  101. /** The fake channel to use is useFakeChannel is enabled. */
  102. private XmppFakeChannel fakeChannel;
  103. /**
  104. * Creates a new XMPP parser for the specified address.
  105. *
  106. * @param address The address to connect to
  107. */
  108. public XmppParser(final URI address) {
  109. super(address);
  110. if (address.getQuery() == null) {
  111. useFakeChannel = false;
  112. priority = 0;
  113. } else {
  114. final Matcher matcher = PRIORITY_PATTERN.matcher(address.getQuery());
  115. useFakeChannel = address.getQuery().matches("(?i).*(^|&)showchannel($|&).*");
  116. priority = matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
  117. }
  118. LOG.debug(
  119. "XMPP parser created with query string {}, parsed fake channel = {}, priority = {}",
  120. address.getQuery(), useFakeChannel, priority);
  121. }
  122. @Override
  123. public void disconnect(final String message) {
  124. super.disconnect(message);
  125. // TODO: Pass quit message on as presence?
  126. connection.disconnect();
  127. }
  128. @Override
  129. public void joinChannels(final ChannelJoinRequest... channels) {
  130. for (ChannelJoinRequest request : channels) {
  131. final MultiUserChat muc = new MultiUserChat(connection, request.getName().substring(1));
  132. try {
  133. if (request.getPassword() == null) {
  134. muc.join(getLocalClient().getNickname());
  135. } else {
  136. muc.join(getLocalClient().getNickname(), request.getPassword());
  137. }
  138. // TODO: Send callbacks etc
  139. } catch (XMPPException ex) {
  140. // TODO: handle
  141. }
  142. }
  143. }
  144. @Override
  145. public ChannelInfo getChannel(final String channel) {
  146. // TODO: Implement
  147. throw new UnsupportedOperationException("Not supported yet.");
  148. }
  149. @Override
  150. public Collection<? extends ChannelInfo> getChannels() {
  151. return Collections.<ChannelInfo>emptyList();
  152. }
  153. @Override
  154. public int getMaxLength(final String type, final String target) {
  155. return Integer.MAX_VALUE;
  156. }
  157. @Override
  158. public int getMaxLength() {
  159. return Integer.MAX_VALUE;
  160. }
  161. @Override
  162. public LocalClientInfo getLocalClient() {
  163. final String[] parts = parseHostmask(connection.getUser());
  164. // TODO: Cache this
  165. return new XmppLocalClientInfo(this, parts[0], parts[2], parts[1]);
  166. }
  167. @Override
  168. public XmppClientInfo getClient(final String details) {
  169. final String[] parts = parseHostmask(details);
  170. if (!contacts.containsKey(parts[0])) {
  171. contacts.put(parts[0], new XmppClientInfo(this, parts[0], parts[2], parts[1]));
  172. }
  173. return contacts.get(parts[0]);
  174. }
  175. @Override
  176. public void sendRawMessage(final String message) {
  177. // Urgh, hacky horrible rubbish. These commands should call methods.
  178. if (message.toUpperCase().startsWith("WHOIS ")) {
  179. handleWhois(message.split(" ")[1]);
  180. } else if (!message.isEmpty() && message.charAt(0) == '<') {
  181. // Looks vaguely like XML, let's send it.
  182. connection.sendPacket(new Packet() {
  183. @Override
  184. public String toXML() {
  185. return message;
  186. }
  187. });
  188. }
  189. }
  190. /**
  191. * Handles a whois request for the specified target.
  192. *
  193. * @param target The user being WHOIS'd
  194. */
  195. private void handleWhois(final String target) {
  196. // Urgh, hacky horrible rubbish. This should be abstracted.
  197. if (contacts.containsKey(target)) {
  198. final XmppClientInfo client = contacts.get(target);
  199. final String[] userParts = client.getNickname().split("@", 2);
  200. callNumericCallback(311, target, userParts[0], userParts[1], "*", client.getRealname());
  201. for (Map.Entry<String, XmppEndpoint> endpoint : client.getEndpoints().entrySet()) {
  202. callNumericCallback(399, target, endpoint.getKey(),
  203. "(" + endpoint.getValue().getPresence() + ")", "has endpoint");
  204. }
  205. } else {
  206. callNumericCallback(401, target, "No such contact found");
  207. }
  208. callNumericCallback(318, target, "End of /WHOIS.");
  209. }
  210. private void callNumericCallback(final int numeric, final String... args) {
  211. final String[] newArgs = new String[args.length + 3];
  212. newArgs[0] = ":xmpp.server";
  213. newArgs[1] = (numeric < 100 ? "0" : "") + (numeric < 10 ? "0" : "") + numeric;
  214. newArgs[2] = getLocalClient().getNickname();
  215. System.arraycopy(args, 0, newArgs, 3, args.length);
  216. getCallbackManager().publish(new NumericEvent(this, new Date(), numeric, newArgs));
  217. }
  218. @Override
  219. public void sendRawMessage(final String message, final QueuePriority priority) {
  220. sendRawMessage(message);
  221. }
  222. @Override
  223. public StringConverter getStringConverter() {
  224. return new DefaultStringConverter();
  225. }
  226. @Override
  227. public boolean isValidChannelName(final String name) {
  228. return false; // TODO: Implement
  229. }
  230. @Override
  231. public boolean compareURI(final URI uri) {
  232. throw new UnsupportedOperationException("Not supported yet.");
  233. }
  234. @Override
  235. public Collection<? extends ChannelJoinRequest> extractChannels(final URI uri) {
  236. return Collections.<ChannelJoinRequest>emptyList();
  237. }
  238. @Override
  239. public String getNetworkName() {
  240. return "XMPP"; // TODO
  241. }
  242. @Override
  243. public String getServerSoftware() {
  244. return "Unknown"; // TODO
  245. }
  246. @Override
  247. public String getServerSoftwareType() {
  248. return "XMPP"; // TODO
  249. }
  250. @Override
  251. public List<String> getServerInformationLines() {
  252. return Collections.emptyList(); // TODO
  253. }
  254. @Override
  255. public int getMaxTopicLength() {
  256. return 0; // TODO
  257. }
  258. @Override
  259. public String getBooleanChannelModes() {
  260. return ""; // TODO
  261. }
  262. @Override
  263. public String getListChannelModes() {
  264. return ""; // TODO
  265. }
  266. @Override
  267. public int getMaxListModes(final char mode) {
  268. return 0; // TODO
  269. }
  270. @Override
  271. public boolean isUserSettable(final char mode) {
  272. throw new UnsupportedOperationException("Not supported yet.");
  273. }
  274. @Override
  275. public String getParameterChannelModes() {
  276. return ""; // TODO
  277. }
  278. @Override
  279. public String getDoubleParameterChannelModes() {
  280. return ""; // TODO
  281. }
  282. @Override
  283. public String getUserModes() {
  284. return ""; // TODO
  285. }
  286. @Override
  287. public String getChannelUserModes() {
  288. return ""; // TODO
  289. }
  290. @Override
  291. public String getChannelPrefixes() {
  292. return "#";
  293. }
  294. @Override
  295. public long getServerLatency() {
  296. return 1000L; // TODO
  297. }
  298. @Override
  299. public void sendCTCP(final String target, final String type, final String message) {
  300. throw new UnsupportedOperationException("Not supported yet.");
  301. }
  302. @Override
  303. public void sendCTCPReply(final String target, final String type, final String message) {
  304. throw new UnsupportedOperationException("Not supported yet.");
  305. }
  306. @Override
  307. public void sendMessage(final String target, final String message) {
  308. if (!chats.containsKey(target)) {
  309. LOG.debug("Creating new chat for {}", target);
  310. chats.put(target, connection.getChatManager().createChat(target,
  311. new MessageListenerImpl()));
  312. }
  313. try {
  314. chats.get(target).sendMessage(message);
  315. } catch (XMPPException ex) {
  316. // TODO: Handle this
  317. }
  318. }
  319. @Override
  320. public void sendNotice(final String target, final String message) {
  321. throw new UnsupportedOperationException("Not supported yet.");
  322. }
  323. @Override
  324. public void sendAction(final String target, final String message) {
  325. sendMessage(target, "/me " + message);
  326. }
  327. @Override
  328. public void sendInvite(final String channel, final String user) {
  329. throw new UnsupportedOperationException("Not supported yet.");
  330. }
  331. @Override
  332. public void sendWhois(final String nickname) {
  333. // TODO: Implement this
  334. }
  335. @Override
  336. public String getLastLine() {
  337. return "TODO: Implement me";
  338. }
  339. @Override
  340. public String[] parseHostmask(final String hostmask) {
  341. return new XmppProtocolDescription().parseHostmask(hostmask);
  342. }
  343. @Override
  344. public long getPingTime() {
  345. throw new UnsupportedOperationException("Not supported yet.");
  346. }
  347. @Override
  348. public void run() {
  349. if (getURI().getUserInfo() == null || !getURI().getUserInfo().contains(":")) {
  350. getCallbackManager().publish(new ConnectErrorEvent(this, new Date(),
  351. new ParserError(ParserError.ERROR_USER,
  352. "User name and password must be specified in URI", "")));
  353. return;
  354. }
  355. final String[] userInfoParts = getURI().getUserInfo().split(":", 2);
  356. final String[] userParts = userInfoParts[0].split("@", 2);
  357. final ConnectionConfiguration config = new ConnectionConfiguration(getURI().getHost(),
  358. getURI().getPort(), userParts[0]);
  359. config.setSecurityMode(getURI().getScheme().equalsIgnoreCase("xmpps")
  360. ? ConnectionConfiguration.SecurityMode.required
  361. : ConnectionConfiguration.SecurityMode.disabled);
  362. config.setSASLAuthenticationEnabled(true);
  363. config.setReconnectionAllowed(false);
  364. config.setRosterLoadedAtLogin(true);
  365. config.setSocketFactory(getSocketFactory());
  366. connection = new FixedXmppConnection(config);
  367. try {
  368. connection.connect();
  369. connection.addConnectionListener(new ConnectionListenerImpl());
  370. connection.addPacketListener(new PacketListenerImpl(DataInListener.class),
  371. new AcceptAllPacketFilter());
  372. connection.addPacketSendingListener(new PacketListenerImpl(DataOutListener.class),
  373. new AcceptAllPacketFilter());
  374. connection.getChatManager().addChatListener(new ChatManagerListenerImpl());
  375. try {
  376. connection.login(userInfoParts[0], userInfoParts[1], "DMDirc.");
  377. } catch (XMPPException ex) {
  378. getCallbackManager().publish(new ConnectErrorEvent(this, new Date(),
  379. new ParserError(ParserError.ERROR_USER, ex.getMessage(), "")));
  380. return;
  381. }
  382. connection.sendPacket(new Presence(Presence.Type.available, null, priority,
  383. Presence.Mode.available));
  384. connection.getRoster().addRosterListener(new RosterListenerImpl());
  385. stateManager = ChatStateManager.getInstance(connection);
  386. setServerName(connection.getServiceName());
  387. getCallbackManager().publish(new ServerReadyEvent(this, new Date()));
  388. for (RosterEntry contact : connection.getRoster().getEntries()) {
  389. getClient(contact.getUser()).setRosterEntry(contact);
  390. }
  391. if (useFakeChannel) {
  392. fakeChannel = new XmppFakeChannel(this, "&contacts");
  393. getCallbackManager().publish(new ChannelSelfJoinEvent(null, null, fakeChannel));
  394. fakeChannel.updateContacts(contacts.values());
  395. contacts.values().stream().filter(XmppClientInfo::isAway).forEach(client ->
  396. getCallbackManager().publish(
  397. new OtherAwayStateEvent(this, new Date(), client, AwayState.UNKNOWN,
  398. AwayState.AWAY)));
  399. }
  400. } catch (XMPPException ex) {
  401. LOG.debug("Go an XMPP exception", ex);
  402. connection = null;
  403. final ParserError error = new ParserError(ParserError.ERROR_ERROR, "Unable to connect",
  404. "");
  405. if (ex.getWrappedThrowable() instanceof Exception) {
  406. // Pass along the underlying exception instead of an XMPP
  407. // specific one
  408. error.setException((Exception) ex.getWrappedThrowable());
  409. } else {
  410. error.setException(ex);
  411. }
  412. getCallbackManager().publish(new ConnectErrorEvent(this, new Date(), error));
  413. }
  414. }
  415. /**
  416. * Handles a client's away state changing.
  417. *
  418. * @param client The client whose state is changing
  419. * @param isBack True if the client is coming back, false if they're going away
  420. */
  421. public void handleAwayStateChange(final ClientInfo client, final boolean isBack) {
  422. LOG.debug("Handling away state change for {} to {}", client.getNickname(), isBack);
  423. if (useFakeChannel) {
  424. getCallbackManager().publish(new OtherAwayStateEvent(
  425. this, new Date(), client, isBack ? AwayState.AWAY : AwayState.HERE,
  426. isBack ? AwayState.HERE : AwayState.AWAY));
  427. }
  428. }
  429. /**
  430. * Marks the local user as away with the specified reason.
  431. *
  432. * @param reason The away reason
  433. */
  434. public void setAway(final String reason) {
  435. connection.sendPacket(new Presence(Presence.Type.available, reason,
  436. priority, Presence.Mode.away));
  437. getCallbackManager().publish(
  438. new AwayStateEvent(this, new Date(), AwayState.HERE, AwayState.AWAY, reason));
  439. }
  440. /**
  441. * Marks the local user as back.
  442. */
  443. public void setBack() {
  444. connection.sendPacket(new Presence(Presence.Type.available, null,
  445. priority, Presence.Mode.available));
  446. getCallbackManager().publish(
  447. new AwayStateEvent(this, new Date(), AwayState.AWAY, AwayState.HERE, null));
  448. }
  449. @Override
  450. public void setCompositionState(final String host, final CompositionState state) {
  451. LOG.debug("Setting composition state for {} to {}", host, state);
  452. final Chat chat = chats.get(parseHostmask(host)[0]);
  453. final ChatState newState;
  454. switch (state) {
  455. case ENTERED_TEXT:
  456. newState = ChatState.paused;
  457. break;
  458. case TYPING:
  459. newState = ChatState.composing;
  460. break;
  461. case IDLE:
  462. default:
  463. newState = ChatState.active;
  464. break;
  465. }
  466. if (chat != null && stateManager != null) {
  467. try {
  468. stateManager.setCurrentState(newState, chat);
  469. } catch (XMPPException ex) {
  470. // Can't set chat state... Oh well?
  471. LOG.info("Couldn't set composition state", ex);
  472. }
  473. }
  474. }
  475. @Override
  476. public void requestGroupList(final String searchTerms) {
  477. // Do nothing
  478. }
  479. private class ConnectionListenerImpl implements ConnectionListener {
  480. @Override
  481. public void connectionClosed() {
  482. getCallbackManager().publish(new SocketCloseEvent(XmppParser.this, new Date()));
  483. }
  484. @Override
  485. public void connectionClosedOnError(final Exception excptn) {
  486. // TODO: Handle exception
  487. getCallbackManager().publish(new SocketCloseEvent(XmppParser.this, new Date()));
  488. }
  489. @Override
  490. public void reconnectingIn(final int i) {
  491. throw new UnsupportedOperationException("Not supported yet.");
  492. }
  493. @Override
  494. public void reconnectionSuccessful() {
  495. throw new UnsupportedOperationException("Not supported yet.");
  496. }
  497. @Override
  498. public void reconnectionFailed(final Exception excptn) {
  499. throw new UnsupportedOperationException("Not supported yet.");
  500. }
  501. }
  502. private class RosterListenerImpl implements RosterListener {
  503. @Override
  504. public void entriesAdded(final Collection<String> clctn) {
  505. // Do nothing, yet
  506. }
  507. @Override
  508. public void entriesUpdated(final Collection<String> clctn) {
  509. // Do nothing, yet
  510. }
  511. @Override
  512. public void entriesDeleted(final Collection<String> clctn) {
  513. // Do nothing, yet
  514. }
  515. @Override
  516. public void presenceChanged(final Presence prsnc) {
  517. getClient(prsnc.getFrom()).setPresence(prsnc);
  518. }
  519. }
  520. private class ChatManagerListenerImpl implements ChatManagerListener {
  521. @Override
  522. public void chatCreated(final Chat chat, final boolean bln) {
  523. if (!bln) {
  524. // Only add chats that weren't created locally
  525. chats.put(parseHostmask(chat.getParticipant())[0], chat);
  526. chat.addMessageListener(new MessageListenerImpl());
  527. }
  528. }
  529. }
  530. private class MessageListenerImpl implements ChatStateListener {
  531. @Override
  532. public void processMessage(final Chat chat, final Message msg) {
  533. if (msg.getType() == Message.Type.error) {
  534. getCallbackManager().publish(new NumericEvent(XmppParser.this, new Date(), 404,
  535. new String[]{":xmpp", "404", getLocalClient().getNickname(), msg.getFrom(),
  536. "Cannot send message: " + msg.getError().toString()}));
  537. return;
  538. }
  539. if (msg.getBody() != null) {
  540. if (msg.getBody().startsWith("/me ")) {
  541. getCallbackManager().publish(new PrivateActionEvent(XmppParser.this, new Date(),
  542. msg.getBody().substring(4), msg.getFrom()));
  543. } else {
  544. getCallbackManager().publish(
  545. new PrivateMessageEvent(XmppParser.this, new Date(), msg.getBody(),
  546. msg.getFrom()));
  547. }
  548. }
  549. }
  550. @Override
  551. public void stateChanged(final Chat chat, final ChatState cs) {
  552. final CompositionState state;
  553. switch (cs) {
  554. case paused:
  555. state = CompositionState.ENTERED_TEXT;
  556. break;
  557. case composing:
  558. state = CompositionState.TYPING;
  559. break;
  560. case active:
  561. case gone:
  562. case inactive:
  563. default:
  564. state = CompositionState.IDLE;
  565. break;
  566. }
  567. getCallbackManager().publish(
  568. new CompositionStateChangeEvent(XmppParser.this, new Date(), state,
  569. chat.getParticipant()));
  570. }
  571. }
  572. private class PacketListenerImpl implements PacketListener {
  573. private final Class<? extends CallbackInterface> callback;
  574. public PacketListenerImpl(final Class<? extends CallbackInterface> callback) {
  575. this.callback = callback;
  576. }
  577. @Override
  578. public void processPacket(final Packet packet) {
  579. if (callback.equals(DataOutListener.class)) {
  580. getCallbackManager().publish(
  581. new DataOutEvent(XmppParser.this, new Date(), packet.toXML()));
  582. } else {
  583. getCallbackManager().publish(
  584. new DataInEvent(XmppParser.this, new Date(), packet.toXML()));
  585. }
  586. }
  587. }
  588. private static class AcceptAllPacketFilter implements PacketFilter {
  589. @Override
  590. public boolean accept(final Packet packet) {
  591. return true;
  592. }
  593. }
  594. }