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