/* * Copyright 2003,2004 Colin Crist * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package hermes.browser.actions; import hermes.Domain; import hermes.Hermes; import hermes.HermesException; import hermes.browser.HermesBrowser; import hermes.browser.IconCache; import hermes.browser.components.EditedMessageHandler; import hermes.browser.components.HierarchicalMessageHeaderTable; import hermes.browser.components.MessageHeaderTable; import hermes.browser.components.MessagePayloadPanel; import hermes.browser.components.MessagesDeleteable; import hermes.browser.components.NavigableComponent; import hermes.browser.components.PopupMenuFactory; import hermes.browser.model.MessageHeaderTableModel; import hermes.browser.tasks.MessageTaskListener; import hermes.browser.tasks.Task; import hermes.config.DestinationConfig; import hermes.swing.FilterablePanel; import hermes.swing.SQL92FilterableTableModel; import hermes.swing.SwingRunner; import hermes.swing.SwingUtils; import java.awt.BorderLayout; import java.awt.event.MouseEvent; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import javax.jms.JMSException; import javax.jms.Message; import javax.swing.Icon; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JSplitPane; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.border.EtchedBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.TableModel; import org.apache.log4j.Logger; import com.codestreet.selector.parser.InvalidSelectorException; import com.jidesoft.document.DocumentComponentEvent; import com.jidesoft.document.DocumentComponentListener; import com.jidesoft.grid.HierarchicalTable; import com.jidesoft.swing.JideScrollPane; /** * HermesAction to perform the browse of a queue or topic. * * @author colincrist@hermesjms.com * @version $Id: BrowserAction.java,v 1.50 2007/01/26 13:26:22 colincrist Exp $ */ public abstract class BrowserAction extends AbstractDocumentComponent implements ListSelectionListener, DocumentComponentListener, MessageTaskListener, FilterableAction, NavigableComponent, MessagesDeleteable { private static final Logger log = Logger.getLogger(BrowserAction.class); private static final Icon queueIcon = IconCache.getIcon(IconCache.QUEUE); private static final Timer timer = new Timer(); private static final Icon topicIcon = IconCache.getIcon(IconCache.TOPIC); /** * Get the name to display in the title bar. */ public static String getDisplayName(Hermes hermes, DestinationConfig config, String postfix) { String rval = null; if (hermes != null) { rval = hermes.getId() + " " + config.getName() + " " + (config.getSelector() != null ? config.getSelector() : "") + (config.isDurable() ? " name=" + config.getClientID() : ""); } else { rval = config.getName() + " " + (config.getSelector() != null ? config.getSelector() : "") + (config.isDurable() ? " name=" + config.getClientID() : ""); } return postfix == null ? rval : rval + " for " + postfix; } private final List<Message> cachedRows = new ArrayList<Message>(); private final JLabel statusMessage = new JLabel(); private boolean firstMessage = true; private Hermes hermes; private long lastMessagePerSecond = 0; private long lastMessagesRead = 0; private ListSelectionEvent lastSelected; private int maxMessages = 1000; private long maxMessagesPerSecond = 0; private final MessageHeaderTable messageHeaderTable; private final MessageHeaderTableModel messageHeaderTableModel; private SQL92FilterableTableModel filterModel; private final MessagePayloadPanel messagePayloadPanel; private JPopupMenu popup; private JideScrollPane headerScrollPane; private TimerTask rateTask; private int readMessages = 0; private int screenUpdateTimeout = 100; private JPanel statusPanel; private Task task; private JPanel topPanel; private long totalMessagesRead = 0; private TimerTask uiUpdateTimer; private TimerTask autoBrowseTimer; private boolean autoBrowse = false; private int selectedRow = -1; private boolean taskStopped = false; private DestinationConfig dConfig; private String postfix; public BrowserAction(Hermes hermes, DestinationConfig dConfig, int maxMessages, String postfix) throws JMSException { super(new JPanel(), getDisplayName(hermes, dConfig, postfix)); this.hermes = hermes; this.dConfig = dConfig; this.messageHeaderTableModel = new MessageHeaderTableModel(hermes, dConfig.getName()); this.messageHeaderTable = new MessageHeaderTable(hermes, this, messageHeaderTableModel, getEditedMessageHandler()); this.maxMessages = maxMessages; this.messagePayloadPanel = new MessagePayloadPanel(dConfig.getName()); this.postfix = postfix; topPanel = (JPanel) getComponent(); } public void updateMessage(Message message) throws JMSException { messageHeaderTableModel.updateMessage(message) ; if (messagePayloadPanel.getMessage().getJMSMessageID().equals(message.getJMSMessageID())) { updatePayloadPanel() ; } } public EditedMessageHandler getEditedMessageHandler() { return null ; } public DestinationConfig getDestinationConfig() { return dConfig; } public String getName() { if (dConfig.getDomain() == Domain.QUEUE.getId()) { return "Q " + super.getName(); } else { return "T " + super.getName(); } } public ListSelectionModel getListSelectionModel() { return messageHeaderTable.getSelectionModel(); } public boolean isNavigableForward() { return getMessageHeaderTable().getSelectedRow() < getMessageHeaderTable().getRowCount() - 1; } public boolean isNavigableBackward() { return getMessageHeaderTable().getSelectedRow() > 0 && getMessageHeaderTable().getRowCount() > 1; } public void navigateBackward() { decrementSelection(); } public void navigateForward() { incrementSelection(); } @Override public String getTitle() { return getDisplayName(hermes, dConfig, postfix); } public DestinationConfig getConfig() { return dConfig; } public String getSelector() { return dConfig.getSelector(); } public Domain getDomain() { return Domain.getDomain(dConfig.getDomain()); } public boolean isRunning() { return !taskStopped; } protected abstract Task createTask() throws Exception; public void documentComponentMoved(DocumentComponentEvent arg0) { // NOP } public void documentComponentMoving(DocumentComponentEvent arg0) { // NOP } /* * (non-Javadoc) * * @see * com.jidesoft.document.DocumentComponentListener#documentComponentActivated * (com.jidesoft.document.DocumentComponentEvent) */ public void documentComponentActivated(DocumentComponentEvent arg0) { // NOP } /* * (non-Javadoc) * * @see * com.jidesoft.document.DocumentComponentListener#documentComponentClosed * (com.jidesoft.document.DocumentComponentEvent) */ public void documentComponentClosed(DocumentComponentEvent arg0) { log.debug("documentClosed " + getName()); messageHeaderTableModel.clear(); if (task != null) { task.stop(); } if (autoBrowseTimer != null) { autoBrowseTimer.cancel(); } autoBrowse = false; } public void documentComponentClosing(DocumentComponentEvent arg0) { // NOP } public void documentComponentDeactivated(DocumentComponentEvent arg0) { // NOP } public void documentComponentOpened(DocumentComponentEvent arg0) { // NOP } public void decrementSelection() { int currentRow = getMessageHeaderTable().getSelectedRow(); getMessageHeaderTable().getSelectionModel().setSelectionInterval(currentRow - 1, currentRow - 1); } public void incrementSelection() { final int currentRow = getMessageHeaderTable().getSelectedRow(); getMessageHeaderTable().getSelectionModel().setSelectionInterval(currentRow + 1, currentRow + 1); } public void enrichPopup(JPopupMenu popupMenu) { } /** * Create and display popup for deleting or saving selected messages * * @param e */ public void doPopup(MouseEvent e) { if (popup == null) { popup = PopupMenuFactory.createBrowseActionPopup(); enrichPopup(popup); } popup.show(messageHeaderTable, e.getX(), e.getY()); } public String getDestination() { return dConfig.getName(); } public Hermes getHermes() { return hermes; } public Icon getIcon() { if (dConfig.getDomain() == Domain.QUEUE.getId()) { return queueIcon; } else { if (dConfig.isDurable()) { return IconCache.getIcon("jms.durableTopic"); } else { return topicIcon; } } } public MessageHeaderTable getMessageHeaderTable() { return messageHeaderTable; } /** * Get the set of JMS message IDS in the selection (if any) */ public Set<String> getSelectedMessageIDs() throws JMSException { Set<String> ids = new HashSet<String>(); int[] rows = messageHeaderTable.getSelectedRows(); for (int i = 0; i < rows.length; i++) { Message m = messageHeaderTableModel.getMessageAt(filterModel.getActualRowAt(rows[i])); if (m.getJMSMessageID() == null) { throw new HermesException("One or more of the messages has a null JMSMessageID"); } try { ids.add(m.getJMSMessageID()); } catch (Exception ex) { log.error("calling getJMSMessageID() on message " + m + ": " + ex.getMessage(), ex); } } return ids; } public boolean hasSelection() { return messageHeaderTable.getSelectedRowCount() > 0; } /** * Get the set of JMS messages in the selection (if any) */ public Collection<Message> getSelectedMessages() { final ArrayList<Message> ids = new ArrayList<Message>(); final int[] rows = messageHeaderTable.getSelectedRows(); for (int i = 0; i < rows.length; i++) { final Message m = messageHeaderTableModel.getMessageAt(filterModel.getActualRowAt(rows[i])); try { ids.add(m); } catch (Exception ex) { log.error("calling getJMSMessageID() on message " + m + ": " + ex.getMessage(), ex); } } return ids; } private JPanel getStatusPanel() { if (statusPanel == null) { statusPanel = new JPanel(); statusPanel.setLayout(new java.awt.BorderLayout()); statusPanel.setAlignmentY(java.awt.Component.BOTTOM_ALIGNMENT); statusMessage.setText("Connecting..."); statusMessage.setBorder(new EtchedBorder()); statusPanel.add(statusMessage); } return statusPanel; } public String getTooltip() { return getTitle(); } public void init() throws JMSException { initUI(); refresh(); } private void initUI() throws JMSException { headerScrollPane = new JideScrollPane(); filterModel = new SQL92FilterableTableModel(messageHeaderTableModel); filterModel.setRowValueProvider(messageHeaderTableModel); messageHeaderTable.setModel(filterModel); topPanel.setLayout(new BorderLayout()); if (HermesBrowser.getBrowser().getConfig().isEmbeddedMessageInBrowsePane()) { topPanel.add(headerScrollPane, "Center"); final HierarchicalTable hTable = new HierarchicalMessageHeaderTable(this, messageHeaderTableModel); headerScrollPane.setViewportView(hTable); hTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // hTable.setExpandableColumn(-1); hTable.setSingleExpansion(false); } else { // // The window is split with the messages read at the top and the // payload and stats at the bottom headerScrollPane.setViewportView(messageHeaderTable); JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane.setDividerLocation(200); splitPane.setOneTouchExpandable(false); splitPane.setContinuousLayout(true); splitPane.add(headerScrollPane, "top"); splitPane.add(messagePayloadPanel, "bottom"); topPanel.add(splitPane, "Center"); // Add to the desktop pane and register it for the Windows // menu... messageHeaderTable.getSelectionModel().addListSelectionListener(this); } JPanel bottomPanel = new JPanel(new BorderLayout()); bottomPanel.add(new FilterablePanel(), BorderLayout.NORTH); bottomPanel.add(getStatusPanel(), BorderLayout.SOUTH); topPanel.add(bottomPanel, BorderLayout.SOUTH); HermesBrowser.getBrowser().addDocumentComponent(this); addDocumentComponentListener(this); } /* * (non-Javadoc) * * @see javax.jms.MessageListener#onMessage(javax.jms.Message) */ public void onMessage(Task task, Message message) { synchronized (cachedRows) { cachedRows.add(message); totalMessagesRead++; } } /* * (non-Javadoc) * * @see hermes.browser.tasks.TaskListener#onStarted() */ public void onStarted(Task task) { // NOP } /* * (non-Javadoc) * * @see hermes.browser.tasks.TaskListener#onStatus(java.lang.String) */ public void onStatus(Task task, final String status) { SwingRunner.invokeLater(new Runnable() { public void run() { statusMessage.setText(status); } }); } public abstract boolean isRefreshable(); /* * (non-Javadoc) * * @see hermes.browser.tasks.TaskListener#onStopped() */ public void onStopped(Task task) { try { updateTableRows(); taskStopped = true; SwingUtilities.invokeLater(new Runnable() { public void run() { messageHeaderTableModel.setFinalMessageIndex(readMessages - 1); if (selectedRow >= 0) { messageHeaderTable.getSelectionModel().setSelectionInterval(selectedRow, selectedRow); valueChanged(null); } } }); if (autoBrowse && isRefreshable()) { autoBrowseTimer = new TimerTask() { @Override public void run() { refresh(); } }; log.debug("scheduling browser refresh for " + getDestination() + " on session " + getHermes().getId() + " in " + HermesBrowser.getBrowser().getConfig().getAutoBrowseRefreshRate() + "s"); timer.schedule(autoBrowseTimer, HermesBrowser.getBrowser().getConfig().getAutoBrowseRefreshRate() * 1000); } } catch (HermesException e) { log.error(e.getMessage(), e); } } public TableModel getTableModel() { return getMessageHeaderTable().getModel(); } public boolean isDeleteable() { return hasSelection(); } public boolean isAutoBrowse() { return autoBrowse; } public void setAutoBrowse(boolean autoBrowse) { this.autoBrowse = autoBrowse; if (autoBrowse) { if (taskStopped) { refresh(); } } else { if (autoBrowseTimer != null) { autoBrowseTimer.cancel(); } } } /* * (non-Javadoc) * * @see hermes.browser.tasks.TaskListener#onThrowable(java.lang.Throwable) */ public void onThrowable(Task task, final Throwable t) { SwingRunner.invokeLater(new Runnable() { public void run() { statusMessage.setText(t.getMessage()); } }); } public void refresh() { readMessages = 0; maxMessagesPerSecond = 0; lastMessagePerSecond = 0; lastMessagesRead = 0; cachedRows.clear(); if (task != null) { task.stop(); } if (rateTask != null) { rateTask.cancel(); } if (uiUpdateTimer != null) { uiUpdateTimer.cancel(); } // // This timer will ensure that the update events to the table // caused by new messages are only applied at most every 70ms // so helping to keep the UI live. uiUpdateTimer = new TimerTask() { public void run() { BrowserAction.this.updateTableRows(); if (task != null && !task.isRunning()) { cancel(); } } }; timer.schedule(uiUpdateTimer, screenUpdateTimeout, screenUpdateTimeout); // // This timer calculates the number of messages per second. rateTask = new TimerTask() { public void run() { synchronized (cachedRows) { lastMessagePerSecond = totalMessagesRead - lastMessagesRead; if (lastMessagePerSecond > maxMessagesPerSecond) { maxMessagesPerSecond = lastMessagePerSecond; } lastMessagesRead = totalMessagesRead; } if (task != null && task.isRunning()) { cancel(); } } }; timer.schedule(rateTask, 1000, 1000); // // Finally create and start the task. try { taskStopped = false; task = createTask(); task.addTaskListener(this); task.start(); } catch (Exception e) { log.error(e.getMessage(), e); } } public void setDestinationName(String destinationName) { messageHeaderTableModel.setDestinationName(destinationName); } public void setStatusText(String text) { statusMessage.setText(text); } public String toShortString() throws JMSException { return hermes.getMetaData().getShortName() + ": " + getDestination(); } /* * (non-Javadoc) * * @see * com.jidesoft.document.DocumentComponentListener#documentComponentClosing * (com.jidesoft.document.DocumentComponentEvent) */ public String toString() { try { return "browse " + toShortString(); } catch (JMSException ex) { return super.toString(); } } /** * Called buy the timer, it will update the UI with the new rows of consumes * messages, updating the status panels accordingly. Returns true if the * action is still running, otherwise false, allowing the timer to switch * itself off. */ public void updateTableRows() { try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { synchronized (cachedRows) { for (Iterator<Message> iter = cachedRows.iterator(); iter.hasNext();) { try { messageHeaderTableModel.addMessage(readMessages++, iter.next()); if (firstMessage) { messageHeaderTable.getSelectionModel().setSelectionInterval(0, 0); firstMessage = false; } } catch (JMSException e) { log.error(e.getMessage(), e); } } boolean autoScroll = cachedRows.size() > 0; cachedRows.clear(); while (maxMessages > 0 && messageHeaderTableModel.getRowCount() > maxMessages) { messageHeaderTableModel.removeFirstRow(); } try { if (autoScroll && HermesBrowser.getBrowser().getConfig().isScrollMessagesDuringBrowse()) { SwingUtils.scrollVertically( messageHeaderTable, SwingUtils.getRowBounds(messageHeaderTable, messageHeaderTableModel.getRowCount(), messageHeaderTableModel.getRowCount())); } } catch (HermesException ex) { log.error(ex.getMessage(), ex); } } StringBuffer buffer = new StringBuffer(); if (task != null && !task.isRunning()) { buffer.append("Finished. "); } switch (messageHeaderTableModel.getRowCount()) { case 0: buffer.append("No messages read."); break; case 1: buffer.append("1 message read."); break; default: buffer.append(readMessages).append(" messages read."); } if (readMessages > maxMessages) { // buffer.append(" (only keeping last " + maxMessages + // " // messages in table)"); } if ((task != null && !task.isRunning()) || messageHeaderTableModel.getRowCount() > 0) { statusMessage.setText(buffer.toString()); } } }); } catch (InterruptedException e) { log.error(e.getMessage(), e); } catch (InvocationTargetException e) { log.error(e.getMessage(), e); } } /** * When a user selects a row, bring up the messages in the below panel. Uses * the MessengerRenderer's that have been configured with the first one * returning a panel being the one used. */ public void valueChanged(ListSelectionEvent e) { updatePayloadPanel(); } private void updatePayloadPanel() { selectedRow = filterModel.getActualRowAt(messageHeaderTable.getActualRowAt(messageHeaderTable.getSelectedRow())); if (messageHeaderTableModel.getRowCount() > selectedRow && selectedRow >= 0) { final int row = selectedRow; final Message m = messageHeaderTableModel.getMessageAt(row); // // Keep the selected row visible. messageHeaderTable.scrollRectToVisible(messageHeaderTable.getCellRect(selectedRow, 0, true)); if (m != null) { messagePayloadPanel.setMessage(hermes, m); } } } public void documentComponentDocked(DocumentComponentEvent arg0) { // TODO Auto-generated method stub } public void documentComponentFloated(DocumentComponentEvent arg0) { // TODO Auto-generated method stub } public void delete() { try { HermesBrowser.getBrowser().getActionFactory().createTruncateAction(hermes, getConfig(), getSelectedMessageIDs(), true, this); } catch (JMSException ex) { HermesBrowser.getBrowser().showErrorDialog(ex); } } public void setSelector(String selector) throws InvalidSelectorException { filterModel.setSelector(selector); } }