001 /*
002 * Copyright (C) 2003 Adam Olsen
003 * This program is free software; you can redistribute it and/or modify
004 * it under the terms of the GNU General Public License as published by
005 * the Free Software Foundation; either version 1, or (at your option)
006 * any later version.
007 * This program is distributed in the hope that it will be useful,
008 * but WITHOUT ANY WARRANTY; without even the implied warranty of
009 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
010 * GNU General Public License for more details.
011 * You should have received a copy of the GNU General Public License
012 * along with this program; if not, write to the Free Software
013 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
014 */
015 package com.valhalla.jbother;
016
017 import java.awt.BorderLayout;
018 import java.awt.Color;
019 import java.awt.Dimension;
020 import java.awt.Toolkit;
021 import java.awt.event.ActionEvent;
022 import java.awt.event.ActionListener;
023 import java.awt.event.MouseAdapter;
024 import java.awt.event.MouseEvent;
025 import java.io.File;
026 import java.io.FileOutputStream;
027 import java.util.ArrayList;
028 import java.util.Iterator;
029 import java.util.Locale;
030 import java.util.Properties;
031 import java.util.ResourceBundle;
032 import java.util.Vector;
033
034 import javax.swing.BorderFactory;
035 import javax.swing.Box;
036 import javax.swing.BoxLayout;
037 import javax.swing.JButton;
038 import javax.swing.JDialog;
039 import javax.swing.JFrame;
040 import javax.swing.JLabel;
041 import javax.swing.JMenuItem;
042 import javax.swing.JPanel;
043 import javax.swing.JPopupMenu;
044 import javax.swing.JScrollPane;
045 import javax.swing.JTable;
046 import javax.swing.SwingUtilities;
047 import javax.swing.event.TableModelEvent;
048 import javax.swing.table.AbstractTableModel;
049 import javax.swing.table.TableColumn;
050
051 import org.jivesoftware.smack.XMPPException;
052 import org.jivesoftware.smackx.ServiceDiscoveryManager;
053 import org.jivesoftware.smackx.packet.DiscoverInfo;
054 import org.jivesoftware.smackx.packet.DiscoverItems;
055
056 import com.valhalla.gui.*;
057 import com.valhalla.jbother.groupchat.GroupChatBookmarks;
058
059 /**
060 * For browsing Jabber services Displays a dialog for browsing services on
061 * Jabber entities such as servers according to JEP-0030
062 *
063 *@author Adam olsen
064 *@created May 25, 2005
065 *@version 1.0
066 */
067 public class ServiceDiscoveryDialog extends JDialog {
068 private ResourceBundle resources = ResourceBundle.getBundle(
069 "JBotherBundle", Locale.getDefault());
070
071 private String host;
072
073 private JButton closeButton = new JButton(resources.getString("closeButton"));
074
075 private JButton searchButton = new JButton(resources.getString("search"));
076
077 private ServiceTableModel tableModel = new ServiceTableModel(this);
078
079 private JTable table = new JTable(tableModel);
080
081 private JLabel title = new JLabel(resources.getString("serviceDiscoveryManager"));
082
083 private MJTextField serverField = new MJTextField();
084
085 private JLabel status = new JLabel(resources.getString("status") + ": ");
086
087 private JPanel bottomPanel = new JPanel();
088
089 private JPanel middlePanel = new JPanel(new BorderLayout(5, 5));
090
091 private JPanel topPanel = new JPanel();
092
093 private JLabel hostLabel = new JLabel(resources.getString("host") + ": ");
094
095 private ServiceDiscoveryThread currentThread = null;
096
097 private TablePopupMenu popupMenu = new TablePopupMenu(this, table);
098
099 private Vector history = new Vector();
100
101 private int current = -1;
102
103 private JButton forward = new JButton(Standard.getIcon("images/buttons/Forward24.gif"));
104
105 private JButton back = new JButton(Standard.getIcon("images/buttons/Back24.gif"));
106
107 private Properties cache = JBotherLoader.getDiscoveryCache();
108
109 private boolean writing = false;
110
111
112 /**
113 * Creates a ServiceDiscoveryDialog with parent as it's parent
114 *
115 *@param parent the JFrame that owns this dialog
116 */
117 public ServiceDiscoveryDialog(JFrame parent) {
118 super(parent);
119 setTitle(resources.getString("serviceDiscovery"));
120
121 initComponents();
122 }
123
124
125 /**
126 * Sets up the Dialog layout
127 */
128 private void initComponents() {
129 tableModel.setTable(table);
130 JPanel panel = (JPanel) getContentPane();
131
132 panel.setLayout(new BorderLayout(5, 5));
133 panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
134
135 JScrollPane scrollPane = new JScrollPane(table);
136 scrollPane.getViewport().setBackground(Color.WHITE);
137
138 topPanel.setLayout(new BorderLayout(5, 5));
139 topPanel.add(hostLabel, BorderLayout.WEST);
140 topPanel.add(serverField, BorderLayout.CENTER);
141
142 JPanel navPanel = new JPanel();
143 navPanel.setLayout(new BoxLayout(navPanel, BoxLayout.X_AXIS));
144 forward.setPreferredSize(new Dimension(26, 26));
145 back.setPreferredSize(new Dimension(26, 26));
146 navPanel.add(back);
147 navPanel.add(forward);
148
149 topPanel.add(navPanel, BorderLayout.EAST);
150
151 middlePanel.add(topPanel, BorderLayout.NORTH);
152
153 middlePanel.add(scrollPane, BorderLayout.CENTER);
154
155 panel.add(title, BorderLayout.NORTH);
156 table.setBorder(BorderFactory.createEtchedBorder());
157 panel.add(middlePanel, BorderLayout.CENTER);
158
159 bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS));
160 bottomPanel.add(status);
161
162 bottomPanel.add(Box.createHorizontalGlue());
163 bottomPanel.add(searchButton);
164 bottomPanel.add(closeButton);
165 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
166
167 panel.add(bottomPanel, BorderLayout.SOUTH);
168 pack();
169 setSize(new Dimension(600, 300));
170
171 if (BuddyList.getInstance().checkConnection()) {
172 serverField.setText(BuddyList.getInstance().getConnection()
173 .getHost());
174 }
175
176 ActionListener listener =
177 new ActionListener() {
178 public void actionPerformed(ActionEvent e) {
179 runServiceDiscovery(null);
180 addHistoryItem(serverField.getText());
181 }
182 };
183
184 addHistoryItem(serverField.getText());
185
186 serverField.addActionListener(listener);
187 searchButton.addActionListener(listener);
188
189 Standard.cascadePlacement(this);
190
191 closeButton.addActionListener(
192 new ActionListener() {
193 public void actionPerformed(ActionEvent e) {
194 writeServiceDiscoveryCache();
195 dispose();
196 }
197 });
198
199 table.addMouseListener(new PopupMouseListener());
200
201 forward.addActionListener(
202 new ActionListener() {
203 public void actionPerformed(ActionEvent e) {
204 forwardHandler();
205 }
206 });
207
208 back.addActionListener(
209 new ActionListener() {
210 public void actionPerformed(ActionEvent e) {
211 backHandler();
212 }
213 });
214
215 back.setEnabled(false);
216 forward.setEnabled(false);
217 runServiceDiscovery(null);
218 }
219
220
221 /**
222 * Description of the Method
223 */
224 private void writeServiceDiscoveryCache() {
225 if (writing) {
226 return;
227 }
228 writing = true;
229
230 File file = new File(JBother.settingsDir + File.separatorChar
231 + "discocache.properties");
232
233 try {
234 FileOutputStream stream = new FileOutputStream(file);
235 cache.store(stream, "");
236 stream.close();
237 } catch (Exception ex) {
238
239 }
240
241 writing = false;
242 }
243
244
245 /**
246 * Description of the Method
247 */
248 private void backHandler() {
249 current--;
250 String id = (String) history.get(current);
251 if (id == null) {
252 current++;
253 return;
254 }
255 forward.setEnabled(true);
256 if (current == 0) {
257 back.setEnabled(false);
258 }
259 runServiceDiscovery(id);
260 }
261
262
263 /**
264 * Description of the Method
265 */
266 private void forwardHandler() {
267 current++;
268 String id = (String) history.get(current);
269 if (id == null) {
270 current--;
271 return;
272 }
273
274 back.setEnabled(true);
275 if (current == history.size() - 1) {
276 forward.setEnabled(false);
277 }
278 runServiceDiscovery(id);
279 }
280
281
282 /**
283 * Adds a feature to the HistoryItem attribute of the
284 * ServiceDiscoveryDialog object
285 *
286 *@param id The feature to be added to the HistoryItem attribute
287 */
288 protected void addHistoryItem(String id) {
289 for (int i = 0; i > current + 1; i--) {
290 history.remove(i);
291 }
292
293 history.add(id);
294 current++;
295 forward.setEnabled(false);
296 back.setEnabled(true);
297 }
298
299
300 /**
301 * Listens for mouse events
302 *
303 *@author Adam Olsen
304 *@created May 25, 2005
305 *@version 1.0
306 */
307 class PopupMouseListener extends MouseAdapter {
308 /**
309 * Description of the Method
310 *
311 *@param e Description of the Parameter
312 */
313 public void mousePressed(MouseEvent e) {
314 checkPop(e);
315 }
316
317
318 /**
319 * Description of the Method
320 *
321 *@param e Description of the Parameter
322 */
323 public void mouseReleased(MouseEvent e) {
324 checkPop(e);
325 }
326
327
328 /**
329 * Description of the Method
330 *
331 *@param e Description of the Parameter
332 */
333 public void mouseClicked(MouseEvent e) {
334 if (e.getClickCount() >= 2) {
335 int row = table.getSelectedRow();
336 if (row < 0) {
337 return;
338 }
339
340 String id = (String) tableModel.getValueAt(row, 1);
341 runServiceDiscovery(id);
342 addHistoryItem(id);
343 } else {
344 checkPop(e);
345 }
346 }
347
348
349 /**
350 * Description of the Method
351 *
352 *@param e Description of the Parameter
353 */
354 public void checkPop(MouseEvent e) {
355 if (e.isPopupTrigger()) {
356 popupMenu.popup(e);
357 }
358 }
359 }
360
361
362 /**
363 * Starts the service discovery thread on the specified server
364 *
365 *@param server the server to run discovery on
366 */
367 protected void runServiceDiscovery(String server) {
368 if (!BuddyList.getInstance().checkConnection()) {
369 BuddyList.getInstance().connectionError();
370 return;
371 }
372
373 if (server != null) {
374 serverField.setText(server);
375 }
376
377 if (serverField.getText().equals("")) {
378 Toolkit.getDefaultToolkit().beep();
379 return;
380 }
381
382 status.setText(resources.getString("status") + ": "
383 + resources.getString("collecting") + " ...");
384 tableModel.clear();
385
386 if (currentThread != null) {
387 currentThread.abortDiscovery();
388 }
389 currentThread = new ServiceDiscoveryThread();
390 new Thread(currentThread).start();
391 }
392
393
394 /**
395 * The thread that actually collects disco information about the server
396 *
397 *@author Adam Olsen
398 *@created May 25, 2005
399 *@version 1.0
400 */
401 class ServiceDiscoveryThread implements Runnable {
402 private boolean stopped = false;
403
404 private ArrayList discoItems = new ArrayList();
405
406
407 /**
408 * Main processing method for the ServiceDiscoveryThread object
409 */
410 public void run() {
411 if (!BuddyList.getInstance().checkConnection()) {
412 BuddyList.getInstance().connectionError();
413 return;
414 }
415
416 ServiceDiscoveryManager manager = new ServiceDiscoveryManager(
417 BuddyList.getInstance().getConnection());
418
419 // get the discover items for the server
420 try {
421 DiscoverItems items = manager.discoverItems(serverField.getText());
422 Iterator i = items.getItems();
423
424 String top[] = new String[]{serverField.getText(),
425 serverField.getText(), "", "", ""};
426 discoItems.add(top);
427 tableModel.addItem(top);
428
429 while (i.hasNext()) {
430 DiscoverItems.Item item = (DiscoverItems.Item) i.next();
431 if (stopped) {
432 return;
433 }
434
435 final String[] entry = new String[]{item.getName(),
436 item.getEntityID(), "", ""};
437 discoItems.add(entry);
438
439 SwingUtilities.invokeLater(
440 new Runnable() {
441 public void run() {
442 tableModel.addItem(entry);
443 }
444 });
445 }
446
447 for (int icount = 0; icount < discoItems.size(); icount++) {
448 String[] entry = (String[]) discoItems.get(icount);
449 final String id = entry[1];
450
451 status.setText(resources.getString("status") + ": "
452 + resources.getString("gettingFeatures") + " ("
453 + id + ") ...");
454
455 // get the discover info about each item
456 DiscoverInfo info = null;
457
458 try {
459 info = manager.discoverInfo(id);
460 } catch (XMPPException e) {
461 }
462
463 // if the service discovery has been aborted, bail out
464 if (stopped) {
465 return;
466 }
467 tableModel.setDisco(icount, info);
468
469 if (info != null) {
470 final DiscoverInfo tempInfo = info;
471 final int index = icount;
472 SwingUtilities.invokeLater(
473 new Runnable() {
474 public void run() {
475 Iterator identities = tempInfo.getIdentities();
476 while (identities.hasNext()) {
477 DiscoverInfo.Identity identity = (DiscoverInfo.Identity) identities.next();
478 if (stopped) {
479 return;
480 }
481 // set the table information
482 tableModel.setItemInfo(index, identity.getName(), identity.getCategory(),
483 identity.getType());
484
485 if (identity.getCategory()
486 .equals("gateway")) {
487 cache.setProperty(id, identity.getCategory()
488 + " " + identity.getType());
489 }
490 }
491 }
492 });
493 }
494 }
495
496 } catch (XMPPException e) {
497 String message = e.getMessage();
498 if (e.getXMPPError() != null) {
499 message = resources.getString("xmppError"
500 + e.getXMPPError().getCode());
501 }
502 status.setText(resources.getString("error") + ": " + message);
503 return;
504 }
505
506 writeServiceDiscoveryCache();
507
508 SwingUtilities.invokeLater(
509 new Runnable() {
510 public void run() {
511 status.setText(resources.getString("status") + ": "
512 + resources.getString("completed") + ".");
513 }
514 });
515 }
516
517
518 /**
519 * Aborts the service discovery
520 */
521 public void abortDiscovery() {
522 this.stopped = true;
523 }
524 }
525 }
526
527 /**
528 * The popup menu for each of the disco items
529 *
530 *@author Adam Olsen
531 *@created May 25, 2005
532 *@version 1.0
533 */
534
535 class TablePopupMenu extends JPopupMenu {
536
537
538 private ResourceBundle resources = ResourceBundle.getBundle(
539 "JBotherBundle", Locale.getDefault());
540
541 private JTable table;
542
543 private ServiceTableModel model;
544
545 private ServiceDiscoveryDialog dialog;
546
547 private JMenuItem browseItem = new JMenuItem(resources.getString("browse"));
548
549 private JMenuItem registerItem = new JMenuItem(resources.getString("register"));
550
551 private JMenuItem joinItem = new JMenuItem(resources.getString("join"));
552
553 private JMenuItem addItem = new JMenuItem(resources.getString("addToRoster"));
554
555 private JMenuItem searchItem = new JMenuItem(resources.getString("search"));
556
557
558 /**
559 * Default constructor
560 *
561 *@param dialog The ServiceDiscoveryDialog to connect this popup menu to
562 *@param table the table to connect this menu to
563 */
564 public TablePopupMenu(ServiceDiscoveryDialog dialog, JTable table) {
565 this.dialog = dialog;
566 this.table = table;
567 model = (ServiceTableModel) table.getModel();
568
569 add(addItem);
570 add(browseItem);
571 add(registerItem);
572 add(joinItem);
573 add(searchItem);
574
575 // show the add buddy dialog
576 addItem.addActionListener(
577 new ActionListener() {
578 public void actionPerformed(ActionEvent e) {
579 AddBuddyDialog dialog = new AddBuddyDialog();
580 String id = getId();
581 dialog.setBuddyId(id);
582 dialog.setVisible(true);
583 }
584 });
585
586 // the browse item runs service discovery on that item
587 browseItem.addActionListener(
588 new ActionListener() {
589 public void actionPerformed(ActionEvent e) {
590 String id = getId();
591 TablePopupMenu.this.dialog.runServiceDiscovery(id);
592 TablePopupMenu.this.dialog.addHistoryItem(id);
593 }
594 });
595
596 joinItem.addActionListener(
597 new ActionListener() {
598 public void actionPerformed(ActionEvent e) {
599 String id = getId();
600 GroupChatBookmarks.showDialog(id, BuddyList.getInstance()
601 .getConnection().getUser(), "");
602 }
603 });
604
605 // the register item forms a RegistrationForm for that item
606 registerItem.addActionListener(
607 new ActionListener() {
608 public void actionPerformed(ActionEvent e) {
609 String id = getId();
610 new RegistrationForm(BuddyList.getInstance().getContainerFrame(), id).getRegistrationInfo();
611 }
612 });
613
614 // search dialog
615 searchItem.addActionListener(
616 new ActionListener() {
617 public void actionPerformed(ActionEvent e) {
618 String id = getId();
619 SearchDialog searchDialog = new SearchDialog(id);
620 searchDialog.setSize(700, 500);
621 }
622 });
623 }
624
625
626 /**
627 * Returns the ID of the selected row
628 *
629 *@return the id of the row
630 */
631 private String getId() {
632 int row = this.table.getSelectedRow();
633 if (row < 0) {
634 return "";
635 }
636
637 String id = (String) model.getValueAt(row, 1);
638 return id;
639 }
640
641
642 /**
643 * Shows the popup menu
644 *
645 *@param e the mouse event
646 */
647 public void popup(MouseEvent e) {
648 int selectedRow = table.rowAtPoint(e.getPoint());
649 if (selectedRow < 0) {
650 return;
651 }
652 table.setRowSelectionInterval(selectedRow, selectedRow);
653
654 String features = model.getFeatures(selectedRow);
655 if (features.indexOf("r") > -1) {
656 registerItem.setEnabled(true);
657 } else {
658 registerItem.setEnabled(false);
659 }
660
661 if (features.indexOf("b") > -1) {
662 browseItem.setEnabled(true);
663 } else {
664 browseItem.setEnabled(false);
665 }
666
667 if (features.indexOf("j") > -1) {
668 joinItem.setEnabled(true);
669 } else {
670 joinItem.setEnabled(false);
671 }
672
673 if (features.indexOf("s") > -1) {
674 searchItem.setEnabled(true);
675 } else {
676 searchItem.setEnabled(false);
677 }
678
679 validate();
680
681 show(table, e.getX(), e.getY());
682 }
683 }
684
685 /**
686 * The table model for the ServiceDiscoveryDialog table
687 *
688 *@author Adam Olsen
689 *@created May 25, 2005
690 *@version 1.0
691 */
692
693 class ServiceTableModel extends AbstractTableModel {
694
695
696 private ServiceDiscoveryDialog dialog;
697
698 private JTable table;
699
700 private ArrayList items = new ArrayList();
701
702 private String[] columns = new String[]{"Name", "JID", "Category", "Type"};
703
704 private ArrayList infos = new ArrayList();
705
706
707 /**
708 * Sets the table value for this model
709 *
710 *@param table the table that this model represents
711 */
712 public void setTable(JTable table) {
713 this.table = table;
714
715 TableColumn column = null;
716
717 // set the default column widths
718 for (int i = 0; i < getColumnCount(); i++) {
719 column = table.getColumnModel().getColumn(i);
720 if (i < 2) {
721 column.setPreferredWidth(150);
722 }
723 // sport column is bigger
724 else {
725 column.setPreferredWidth(50);
726 }
727 }
728 }
729
730
731 /**
732 * Default Constructor
733 *
734 *@param dialog the ServiceDiscoveryDialog that contains this table
735 */
736 public ServiceTableModel(ServiceDiscoveryDialog dialog) {
737 this.dialog = dialog;
738 }
739
740
741 /**
742 * gets the number of columns in this table
743 *
744 *@return the number of columns
745 */
746 public int getColumnCount() {
747 return columns.length;
748 }
749
750
751 /**
752 * Gets the number of rows in the table
753 *
754 *@return the number of rows in the table
755 */
756 public int getRowCount() {
757 return items.size();
758 }
759
760
761 /**
762 * Returns the name of a specific column
763 *
764 *@param column the column who's name is wanted
765 *@return the name of the column
766 */
767 public String getColumnName(int column) {
768 return columns[column];
769 }
770
771
772 /**
773 * Get the Object for a specific coordinate in the table
774 *
775 *@param row the row of the item
776 *@param column the column of the item
777 *@return the Object at the specified coordinates
778 */
779 public Object getValueAt(int row, int column) {
780 synchronized (items) {
781 String[] item = (String[]) items.get(row);
782 return item[column];
783 }
784 }
785
786
787 /**
788 * gets the features of a specific item (row)
789 *
790 *@param row the index of the row you want information for
791 *@return a string containing all of the features the row supports
792 */
793 public String getFeatures(int row) {
794 synchronized (infos) {
795 String info = "";
796
797 try {
798 info = (String) infos.get(row);
799 } catch (Exception e) {
800 }
801
802 return info;
803 }
804 }
805
806
807 /**
808 * Sets the disco information once it's found
809 *
810 *@param index the index of the row you want to set the information about
811 *@param disco the information about the row
812 */
813 public void setDisco(int index, DiscoverInfo disco) {
814 synchronized (infos) {
815 String info = "";
816 if (disco != null) {
817 if (disco.containsFeature("jabber:iq:register")) {
818 info += "r";
819 }
820 if (disco.containsFeature("jabber:iq:browse")
821 || disco.containsFeature("http://jabber.org/protocol/disco")) {
822 info += "b";
823 }
824 if (disco.containsFeature("muc_public")) {
825 info += "j";
826 }
827
828 if (disco.containsFeature("jabber:iq:search")) {
829 info += "s";
830 }
831 }
832
833 infos.add(index, info);
834 }
835 }
836
837
838 /**
839 * Adds a row to the table
840 *
841 *@param item the array containing the item to add
842 */
843 public void addItem(String[] item) {
844 synchronized (items) {
845 items.add(item);
846 fireTableRowsInserted(items.size(), items.size());
847 }
848 }
849
850
851 /**
852 * Sets information about a specific row
853 *
854 *@param row the row to set
855 *@param name the new name
856 *@param category the category of the row
857 *@param type The new itemInfo value
858 */
859 public void setItemInfo(int row, String name, String category, String type) {
860 synchronized (items) {
861 String[] item = (String[]) items.get(row);
862 item[0] = name;
863 item[2] = category;
864 item[3] = type;
865
866 fireTableRowsUpdated(row, row);
867 }
868 }
869
870
871 /**
872 * Clears the table
873 */
874 public void clear() {
875 items.clear();
876 infos.clear();
877 table.tableChanged(new TableModelEvent(this));
878 }
879 }
880