1 /***
2 * Redistribution and use of this software and associated documentation
3 * ("Software"), with or without modification, are permitted provided
4 * that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain copyright
7 * statements and notices. Redistributions must also contain a
8 * copy of this document.
9 *
10 * 2. Redistributions in binary form must reproduce the
11 * above copyright notice, this list of conditions and the
12 * following disclaimer in the documentation and/or other
13 * materials provided with the distribution.
14 *
15 * 3. The name "Exolab" must not be used to endorse or promote
16 * products derived from this Software without prior written
17 * permission of Exoffice Technologies. For written permission,
18 * please contact info@exolab.org.
19 *
20 * 4. Products derived from this Software may not be called "Exolab"
21 * nor may "Exolab" appear in their names without prior written
22 * permission of Exoffice Technologies. Exolab is a registered
23 * trademark of Exoffice Technologies.
24 *
25 * 5. Due credit should be given to the Exolab Project
26 * (http://www.exolab.org/).
27 *
28 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 * OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * Copyright 2000-2003 (C) Exoffice Technologies Inc. All Rights Reserved.
42 *
43 * $Id: AdminMgr.java,v 1.3 2006/02/23 11:02:57 tanderson Exp $
44 *
45 * Date Author Changes
46 */
47
48
49 package org.exolab.jms.tools.admin;
50
51 import java.awt.*;
52 import java.awt.event.ActionEvent;
53 import java.awt.event.ActionListener;
54 import java.awt.event.MouseAdapter;
55 import java.awt.event.MouseEvent;
56 import java.awt.event.WindowAdapter;
57 import java.awt.event.WindowEvent;
58 import java.io.BufferedReader;
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.io.InputStreamReader;
62 import java.io.PrintStream;
63 import java.util.ArrayList;
64 import javax.swing.*;
65 import javax.swing.event.TreeExpansionEvent;
66 import javax.swing.event.TreeExpansionListener;
67 import javax.swing.tree.DefaultMutableTreeNode;
68 import javax.swing.tree.DefaultTreeModel;
69 import javax.swing.tree.TreePath;
70
71 import org.apache.commons.logging.Log;
72 import org.apache.commons.logging.LogFactory;
73 import org.apache.log4j.xml.DOMConfigurator;
74 import org.apache.oro.text.regex.Pattern;
75 import org.apache.oro.text.regex.PatternMatcherInput;
76 import org.apache.oro.text.regex.Perl5Compiler;
77 import org.apache.oro.text.regex.Perl5Matcher;
78 import org.exolab.jms.config.AdminConfiguration;
79 import org.exolab.jms.config.Configuration;
80 import org.exolab.jms.config.ConfigurationLoader;
81 import org.exolab.jms.util.CommandLine;
82
83
84 /***
85 * This class is the Gui controller for the JMS administration. It displays data
86 * as a hierarchical set of tree nodes.
87 * <p/>
88 * <P>The Root is all the contactable JMS servers, idealy there can be several
89 * of these all on different ports, with one common admin port they all listen
90 * to. A user selects a JMSServer then connects via the menu item. This allows
91 * the admin GUI to connect to the server via the main port and begin displaying
92 * all its Queue/Topics and registered consumers.
93 * <p/>
94 * If there are no queue/topics for a consumer the node will be empty. Selecting
95 * a consumer allows a user to see what its details are, i.e last message
96 * received and acked, whether the consumer is currently active/deactive paused
97 * etc.
98 * <p/>
99 * <P>The Gui can also be used to add/remove queue/topics and consumer
100 * registrations.
101 * <p/>
102 * Reliable Topics are read only, since they cannot be persisted, they simply
103 * display a snapshot at the time of connection.
104 * <p/>
105 * <P>For the moment this is not truly dynamic, that is a refresh needs to be
106 * activated on the Gui to cause an update (other than changes made through the
107 * Gui istelf).
108 *
109 * @author <a href="mailto:mourikis@exolab.org">Jim Mourikis</a>
110 * @version $Revision: 1.3 $ $Date: 2006/02/23 11:02:57 $
111 */
112 public class AdminMgr extends JFrame {
113
114 private JMenu _file;
115 private JMenuItem _exit;
116 private JMenu _connections;
117 private JMenuItem _refresh;
118 private JMenuItem _online;
119 private JMenuItem _offline;
120 private JMenuItem _disconnect;
121 private JMenuItem _startup;
122 private JMenuItem _shutdown;
123 private JTree _serverProperties;
124 private JTextField _messageArea;
125
126 private Configuration _config;
127
128
129 private boolean _connected = false;
130
131 /***
132 * The server start command
133 */
134 private static String _serverStart = null;
135
136 /***
137 * The server configuration file path
138 */
139 private static String _serverConfig = null;
140
141
142 private StreamRedirect _output = null;
143
144 /***
145 * The logger.
146 */
147 private static final Log _log = LogFactory.getLog(AdminMgr.class);
148
149
150 public AdminMgr(String path) throws Exception {
151 _config = new ConfigurationLoader().load(path);
152 initComponents(path);
153 pack();
154 }
155
156 /***
157 * This method is called from within the constructor to initialize the form.
158 * All the GUI objects are created, and callbacks registered.
159 */
160 private void initComponents(String title) {
161 JMenuBar menuBar = new JMenuBar();
162 _file = new JMenu();
163 _exit = new JMenuItem();
164 JMenu actions = new JMenu();
165 _connections = new JMenu();
166 _refresh = new JMenuItem();
167 _online = new JMenuItem();
168 _offline = new JMenuItem();
169 _disconnect = new JMenuItem();
170 JSeparator separator = new JSeparator();
171 _startup = new JMenuItem();
172 _shutdown = new JMenuItem();
173 JScrollPane jmsServers = new JScrollPane();
174 JComboBox jmsCombo = new JComboBox();
175 _serverProperties = new JTree();
176 setTitle("OpenJMS Administrator: " + title);
177 DefaultTreeModel serverModel =
178 OpenJMSServer.createServerList(_serverProperties);
179 _serverProperties.setModel(serverModel);
180 AdminInfo info = new AdminInfo();
181 _serverProperties.setCellRenderer(info);
182
183 _messageArea = new JTextField();
184 _file.setText("File");
185 _exit.setToolTipText("Exit administration");
186 _exit.setText("Exit");
187 _exit.setMnemonic('x');
188
189 _serverProperties.setRootVisible(false);
190 _serverProperties.setShowsRootHandles(true);
191 _serverProperties.putClientProperty("JTree.lineStyle", "Angled");
192 _serverProperties.setCellEditor(new OpenJMSEditor(_serverProperties,
193 jmsCombo));
194 _serverProperties.setEditable(false);
195 setupCallbacks();
196 _file.add(_exit);
197 _file.setMnemonic('F');
198 menuBar.add(_file);
199 actions.setText("Actions");
200 actions.setMnemonic('A');
201 _connections.setText("Connections");
202 _connections.setMnemonic('C');
203 _refresh.setToolTipText("Refresh the display");
204 _online.setToolTipText("Connect to a running OpenJMS Server");
205 _offline.setToolTipText("Connect directly to an OpenJMS database");
206 _refresh.setText("Refresh");
207 _refresh.setMnemonic('R');
208 actions.add(_refresh);
209
210 _online.setText("Online");
211 _online.setMnemonic('O');
212 _offline.setText("Offline");
213 _offline.setMnemonic('f');
214 _connections.add(_online);
215 _connections.add(_offline);
216 actions.add(_connections);
217 _disconnect.setToolTipText
218 ("Disconnect from any connected OpenJMS Servers");
219 _disconnect.setText("Disconnect");
220 _disconnect.setMnemonic('D');
221 actions.add(_disconnect);
222
223 actions.add(separator);
224 _startup.setToolTipText("Start the OpenJMS server");
225 _startup.setText("Start OpenJMS");
226 _startup.setMnemonic('S');
227 _shutdown.setToolTipText("Shutdown the connected OpenJMS server");
228 _shutdown.setText("Shutdown OpenJMS");
229 _shutdown.setMnemonic('h');
230 actions.add(_startup);
231 actions.add(_shutdown);
232 menuBar.add(actions);
233
234 jmsServers.setViewportView(_serverProperties);
235
236
237 getContentPane().add(jmsServers, BorderLayout.CENTER);
238
239 _messageArea.setToolTipText("Message Area");
240 _messageArea.setEditable(false);
241 _messageArea.setForeground(java.awt.Color.red);
242 _messageArea.setText("Not Connected");
243 _messageArea.setHorizontalAlignment(SwingConstants.CENTER);
244
245
246 getContentPane().add(_messageArea, BorderLayout.SOUTH);
247 setJMenuBar(menuBar);
248 _startup.setEnabled(true);
249 _shutdown.setEnabled(false);
250 _refresh.setEnabled(false);
251 _disconnect.setEnabled(false);
252 }
253
254 /***
255 * The exit method for the application, when the user shutdowns the form.
256 */
257 private void exitAdmin() {
258 System.exit(0);
259 }
260
261 /***
262 * Exit the Application when a user selects File->Exit from the menu
263 */
264 private void exitForm() {
265 System.exit(0);
266 }
267
268 /***
269 * Refresh the display, and repaint all items.
270 */
271 private void refresh() {
272 if (AbstractAdminConnection.instance() instanceof OnlineConnection) {
273 setConnected(false, null);
274 setConnected(true, "Connected - Online Mode");
275 _startup.setEnabled(false);
276 _shutdown.setEnabled(true);
277
278 } else {
279 ((OpenJMSServer) (_serverProperties.getModel().getRoot()
280 )).refresh();
281 }
282 }
283
284 /***
285 * Start the OpenJMSServer.
286 */
287 private void startup() {
288
289 try {
290 String args[] = getStartCommand();
291
292 System.out.print("running ");
293 for (int i = 0, j = args.length; i < j; i++) {
294 System.out.print(args[i] + " ");
295 }
296 System.out.println();
297
298 if (_output != null) {
299
300 _output.interrupt();
301 }
302 Process proc = Runtime.getRuntime().exec(args);
303
304 _output = new StreamRedirect(proc.getInputStream());
305
306 _output.start();
307 } catch (Exception err) {
308 JOptionPane.showMessageDialog
309 (this, "Failed to Startup OpenJMSServer:\n" + err
310 + "\nCheck config file",
311 "OpenJMSServer Startup", JOptionPane.ERROR_MESSAGE);
312 }
313 }
314
315 /***
316 * When a user wishes to connect to all known OpenJMSServers. Attempt to
317 * create an RMI connection to the OpenJMSServer. If the server is not
318 * running, this will fail. The user can start the server through the start
319 * server command, and attempt to re-connect, or use the offline method
320 * below.
321 */
322 private void onlineConnect() {
323 try {
324
325 new OnlineConnection(this, _config);
326 _startup.setEnabled(false);
327 _shutdown.setEnabled(true);
328 setConnected(true, "Connected - Online Mode");
329 } catch (Exception err) {
330 JOptionPane.showMessageDialog
331 (this, err.getMessage(), "Online Connection Error",
332 JOptionPane.ERROR_MESSAGE);
333 }
334 }
335
336
337 /***
338 * Connect to the database in offline mode.
339 */
340 private void offlineConnect() {
341 try {
342
343 new OfflineConnection(this, _config);
344 _startup.setEnabled(false);
345 _shutdown.setEnabled(false);
346 setConnected(true, "Connected - OFFLine Mode");
347 } catch (Exception err) {
348 error("Database error", err.getMessage(), err);
349 }
350 }
351
352 /***
353 * Disconnect from a connected OpenJMSServer. Close the database, set the
354 * connected flag to false, stop displaying the OpenJMS folder.
355 */
356 private void disconnect() {
357 try {
358 AbstractAdminConnection.instance().close();
359 setConnected(false, null);
360 } catch (Exception e) {
361 JOptionPane.showMessageDialog
362 (this, e.getMessage(), "Database Close Error",
363 JOptionPane.ERROR_MESSAGE);
364 }
365 }
366
367 /***
368 * A conveniance routine to open/close all database connections, and fix up
369 * the display.
370 * <p/>
371 * <P>When connecting, show the root object, get any persistent queue/topic
372 * names currently in the db, and display them. Turn off the connection
373 * menu, enable the disconnection and shutdown and the context menus. Set
374 * the message area text to connected.
375 * <p/>
376 * <P>When disconnecting, turn off the root, destroy all Gui objects close
377 * the db connection, turn off all context sensitive menus, disable
378 * disconnection menu and enable connection. Set the message text to
379 * disconnected.
380 *
381 * @param c a flag inidication if this is a connection or disconnection.
382 */
383 private void setConnected(boolean c, String st) {
384 if (c) {
385 _serverProperties.setRootVisible(true);
386 ((OpenJMSServer)
387 (_serverProperties.getModel().getRoot())).displayConnections();
388 _connections.setEnabled(false);
389 _refresh.setEnabled(true);
390
391 _disconnect.setEnabled(true);
392
393 _messageArea.setForeground(java.awt.Color.green.darker().darker());
394 _messageArea.setText(st);
395 _connected = true;
396 } else {
397 _serverProperties.setRootVisible(false);
398 OpenJMSServer root =
399 (OpenJMSServer) _serverProperties.getModel().getRoot();
400 root.removeAllChildren();
401 DefaultTreeModel model =
402 (DefaultTreeModel) _serverProperties.getModel();
403 model.nodeStructureChanged((DefaultMutableTreeNode) root);
404 _connections.setEnabled(true);
405 _startup.setEnabled(true);
406 _shutdown.setEnabled(false);
407 _refresh.setEnabled(false);
408 _disconnect.setEnabled(false);
409 _messageArea.setForeground(java.awt.Color.red);
410 _messageArea.setText("Not Connected");
411 _connected = false;
412 }
413 }
414
415 /***
416 * Set up all Action menu callbacks, and mouse events for the tree and its
417 * nodes. Check all mose 2 key presses on a node, select the node, then call
418 * the nodes appropriate display methos to display its specific popup
419 * menus.
420 * <p/>
421 * <P>When first connected, all queue/topics displayed are not expaned. This
422 * is just a performance saver, since their could potentially be hundreds of
423 * objects. A callback is set up, so that when a queue/topic is expanded, it
424 * is lokked up only then to determine what consumers are registered with
425 * it.
426 */
427 private void setupCallbacks() {
428
429 addWindowListener(new WindowAdapter() {
430
431 public void windowClosing(WindowEvent evt) {
432 exitForm();
433 }
434 }
435 );
436
437
438 _serverProperties.addMouseListener(new MouseAdapter() {
439
440 public void mousePressed(MouseEvent e) {
441 if (!_connected) {
442 return;
443 }
444
445 if (SwingUtilities.isRightMouseButton(e)) {
446 int selRow = _serverProperties.getRowForLocation
447 (e.getX(), e.getY());
448
449 _serverProperties.setSelectionRow(selRow);
450 Object loc =
451 _serverProperties.getLastSelectedPathComponent();
452 if (loc instanceof OpenJMSNode) {
453 OpenJMSNode node = (OpenJMSNode) loc;
454 node.displayCommands
455 (_serverProperties.getRowBounds(selRow));
456 } else if (loc instanceof OpenJMSServer) {
457 ((OpenJMSServer) loc).displayCommands
458 (_serverProperties.getRowBounds(selRow));
459 }
460 }
461 }
462 }
463 );
464
465 _serverProperties.addTreeExpansionListener(new TreeExpansionListener() {
466
467 public void treeCollapsed(TreeExpansionEvent e) {
468
469 }
470
471 public void treeExpanded(TreeExpansionEvent e) {
472 TreePath path = e.getPath();
473 Object loc = path.getLastPathComponent();
474 if (loc instanceof OpenJMSNode) {
475 OpenJMSNode node = (OpenJMSNode) loc;
476 node.update();
477 }
478 }
479 }
480 );
481
482 /***
483 _serverProperties.addTreeSelectionListener(new TreeSelectionListener()
484 {
485 public void valueChanged(TreeSelectionEvent e)
486 {
487 TreePath path = e.getPath();
488 Object loc = path.getLastPathComponent();
489 if (loc instanceof OpenJMSNode)
490 {
491 OpenJMSNode node = (OpenJMSNode)loc;
492 System.out.println(node);
493 }
494 }
495 }
496 );
497
498 **/
499 _exit.addActionListener(new ActionListener() {
500
501 public void actionPerformed(ActionEvent evt) {
502 exitAdmin();
503 }
504 }
505 );
506
507
508 _refresh.addActionListener(new ActionListener() {
509
510 public void actionPerformed(ActionEvent evt) {
511 refresh();
512 }
513 }
514 );
515
516
517 _online.addActionListener(new ActionListener() {
518
519 public void actionPerformed(ActionEvent evt) {
520 onlineConnect();
521 }
522 }
523 );
524
525 _offline.addActionListener(new ActionListener() {
526 public void actionPerformed(ActionEvent evt) {
527 offlineConnect();
528 }
529 }
530 );
531
532 _disconnect.addActionListener(new ActionListener() {
533 public void actionPerformed(ActionEvent evt) {
534 disconnect();
535 }
536 }
537 );
538
539 _startup.addActionListener(new ActionListener() {
540 public void actionPerformed(ActionEvent evt) {
541 startup();
542 }
543 }
544 );
545
546 _shutdown.addActionListener(new ActionListener() {
547
548 public void actionPerformed(ActionEvent evt) {
549 try {
550 AbstractAdminConnection.instance().stopServer();
551 setConnected(false, null);
552 } catch (NullPointerException err) {
553 JOptionPane.showMessageDialog
554 (_file, "Must connect with online mode \nto "
555 + "shutdown server", "Shutdown Error",
556 JOptionPane.ERROR_MESSAGE);
557 }
558 }
559 }
560 );
561 }
562
563 /***
564 * The main entry point for this admin gui. The main form and any support
565 * dialogs are created. An initial size is given, and the gui placed in the
566 * middle of the screen
567 *
568 * @param args the command line arguments
569 */
570 public static void main(String args[]) {
571 try {
572 CommandLine cmdline = new CommandLine(args);
573
574 boolean helpSet = cmdline.exists("help");
575 boolean configSet = cmdline.exists("config");
576 boolean stopServer = cmdline.exists("stopServer");
577 String username = cmdline.value("u");
578 String password = cmdline.value("p");
579
580 if (helpSet) {
581 usage();
582 } else if (!configSet && !stopServer && args.length != 0) {
583
584 usage();
585 } else {
586 String configFile = cmdline.value("config");
587 if (configFile == null) {
588 String home = getOpenJMSHome();
589 configFile = home + "/config/openjms.xml";
590 }
591 Configuration config = new ConfigurationLoader().load(configFile);
592 String path = config.getLoggerConfiguration().getFile();
593 if (path != null) {
594 DOMConfigurator.configure(path);
595 }
596 AdminConfiguration adminConfig = null;
597
598 adminConfig = config.getAdminConfiguration();
599 _serverStart = adminConfig.getScript();
600 _serverConfig = adminConfig.getConfig();
601 if (_serverConfig == null) {
602 _serverConfig = configFile;
603 }
604
605 if (stopServer) {
606
607
608
609 new OnlineConnection(username, password, config);
610 AbstractAdminConnection.instance().stopServer();
611 } else {
612 AdminMgr admin = new AdminMgr(configFile);
613 QueryDialog.create(admin);
614 CreateQueueDialog.create(admin);
615 CreateTopicDialog.create(admin);
616 CreateLogonDialog.create(admin);
617 CreateUserDialog.create(admin);
618 ChangePasswordDialog.create(admin);
619 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
620
621 admin.setLocation(screen.width / 2 - 150, screen.height / 2 - 150);
622 admin.setSize(300, 300);
623 admin.invalidate();
624 admin.show();
625 }
626 }
627 } catch (Exception err) {
628 err.printStackTrace();
629 System.err.println("Failed to initialize AdminMgr.\nExiting....");
630 }
631 }
632
633 /***
634 * Print out information on running this sevice
635 */
636 static protected void usage() {
637 PrintStream out = System.out;
638
639 out.println("\n\n");
640 out.println("=====================================================");
641 out.println("Usage information for " + AdminMgr.class.getName());
642 out.println("=====================================================");
643 out.println("\n" + AdminMgr.class.getName());
644 out.println(" [-help | -config <xml config file>]\n");
645 out.println("\t-help displays this screen\n");
646 out.println("\t-config file name of xml-based config file\n");
647 }
648
649 /***
650 * A simple class to re-direct the output stream from JMS to the local
651 * console
652 */
653 class StreamRedirect extends Thread {
654
655 InputStream is_;
656
657 StreamRedirect(InputStream is) {
658 is_ = is;
659 }
660
661 public void run() {
662 try {
663 InputStreamReader isr = new InputStreamReader(is_);
664 BufferedReader br = new BufferedReader(isr);
665 String line = null;
666 while ((line = br.readLine()) != null) {
667 System.out.println(line);
668 }
669 } catch (IOException ioe) {
670 ioe.printStackTrace();
671 }
672 }
673 }
674
675 private String[] getStartCommand() throws Exception {
676 ArrayList args = new ArrayList();
677
678 if (_serverStart != null) {
679 Perl5Compiler compiler = new Perl5Compiler();
680 Pattern pattern = compiler.compile("'.*'|[^//s]*");
681 Perl5Matcher matcher = new Perl5Matcher();
682 PatternMatcherInput input = new PatternMatcherInput(_serverStart);
683
684 while (matcher.contains(input, pattern)) {
685 String arg = matcher.getMatch().toString();
686 if (arg.startsWith("'") && arg.endsWith("'")) {
687 arg = arg.substring(1, arg.length() - 1);
688 }
689 args.add(arg);
690 }
691 }
692
693 args.add("-config");
694 args.add(_serverConfig);
695
696 return (String[]) args.toArray(new String[0]);
697 }
698
699 /***
700 * Returns the value of the openjms.home environment variable
701 */
702 private static String getOpenJMSHome() {
703 return System.getProperty("openjms.home",
704 System.getProperty("user.dir"));
705 }
706
707 /***
708 * Display and log an error
709 *
710 * @param title
711 * @param message
712 * @param exception
713 */
714 private void error(String title, String message, Exception exception) {
715 _log.error(message, exception);
716 JOptionPane.showMessageDialog(this, message, title,
717 JOptionPane.ERROR_MESSAGE);
718 }
719
720 }
721