001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements. See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache license, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License. You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the license for the specific language governing permissions and
015     * limitations under the license.
016     */
017    package org.apache.logging.log4j.core.jmx;
018    
019    import java.awt.BorderLayout;
020    import java.awt.Color;
021    import java.awt.Font;
022    import java.io.IOException;
023    import java.io.PrintWriter;
024    import java.io.StringWriter;
025    import java.util.HashMap;
026    import java.util.Map;
027    
028    import javax.management.InstanceNotFoundException;
029    import javax.management.MalformedObjectNameException;
030    import javax.management.Notification;
031    import javax.management.NotificationFilterSupport;
032    import javax.management.NotificationListener;
033    import javax.management.ObjectName;
034    import javax.management.remote.JMXConnector;
035    import javax.management.remote.JMXConnectorFactory;
036    import javax.management.remote.JMXServiceURL;
037    import javax.swing.JFrame;
038    import javax.swing.JOptionPane;
039    import javax.swing.JPanel;
040    import javax.swing.JScrollPane;
041    import javax.swing.JTabbedPane;
042    import javax.swing.JTextArea;
043    import javax.swing.SwingUtilities;
044    import javax.swing.UIManager;
045    import javax.swing.UIManager.LookAndFeelInfo;
046    
047    /**
048     * GUI that connects to a Java process via JMX and allows the user to view and
049     * modify the log4j2 configuration, as well as monitor status logs.
050     * 
051     * @see http 
052     *      ://docs.oracle.com/javase/6/docs/technotes/guides/management/jconsole
053     *      .html
054     */
055    public class ClientGUI extends JPanel implements NotificationListener {
056        private static final long serialVersionUID = -253621277232291174L;
057        private Client client;
058        private JTextArea statusLogTextArea;
059        private JTabbedPane tabbedPane;
060    
061        public ClientGUI(Client client) throws InstanceNotFoundException,
062                MalformedObjectNameException, IOException {
063            this.client = Assert.isNotNull(client, "client");
064            createWidgets();
065            populateWidgets();
066            registerListeners();
067        }
068    
069        private void createWidgets() {
070            statusLogTextArea = new JTextArea();
071            statusLogTextArea.setEditable(false);
072            statusLogTextArea.setBackground(this.getBackground());
073            statusLogTextArea.setForeground(Color.black);
074            statusLogTextArea.setFont(new Font("Monospaced", Font.PLAIN,
075                    statusLogTextArea.getFont().getSize()));
076            JScrollPane scrollStatusLog = new JScrollPane(statusLogTextArea);
077    
078            tabbedPane = new JTabbedPane();
079            tabbedPane.addTab("StatusLogger", scrollStatusLog);
080    
081            this.setLayout(new BorderLayout());
082            this.add(tabbedPane, BorderLayout.CENTER);
083        }
084    
085        private void populateWidgets() {
086    
087            StatusLoggerAdminMBean statusAdmin = client.getStatusLoggerAdmin();
088            String[] messages = statusAdmin.getStatusDataHistory();
089            for (String message : messages) {
090                statusLogTextArea.append(message + "\n");
091            }
092    
093            for (LoggerContextAdminMBean ctx : client.getLoggerContextAdmins()) {
094                ClientEditConfigPanel editor = new ClientEditConfigPanel(ctx);
095                tabbedPane.addTab("LoggerContext: " + ctx.getName(), editor);
096            }
097        }
098    
099        private void registerListeners() throws InstanceNotFoundException,
100                MalformedObjectNameException, IOException {
101            NotificationFilterSupport filter = new NotificationFilterSupport();
102            filter.enableType(StatusLoggerAdminMBean.NOTIF_TYPE_MESSAGE);
103            ObjectName objName = new ObjectName(StatusLoggerAdminMBean.NAME);
104            client.getConnection().addNotificationListener(objName, this, filter,
105                    null);
106        }
107    
108        @Override
109        public void handleNotification(Notification notif, Object paramObject) {
110            if (StatusLoggerAdminMBean.NOTIF_TYPE_MESSAGE.equals(notif.getType())) {
111                statusLogTextArea.append(notif.getMessage() + "\n");
112            }
113        }
114    
115        /**
116         * Connects to the specified location and shows this panel in a window.
117         * <p>
118         * Useful links:
119         * http://www.componative.com/content/controller/developer/insights
120         * /jconsole3/
121         * 
122         * @param args
123         *            must have at least one parameter, which specifies the location
124         *            to connect to. Must be of the form {@code host:port} or
125         *            {@code service:jmx:rmi:///jndi/rmi://<host>:<port>/jmxrmi} or
126         *            {@code service:jmx:rmi://<host>:<port>/jndi/rmi://<host>:<port>/jmxrmi}
127         */
128        public static void main(String[] args) throws Exception {
129            if (args.length < 1) {
130                usage();
131                return;
132            }
133            String serviceUrl = args[0];
134            if (!serviceUrl.startsWith("service:jmx")) {
135                serviceUrl = "service:jmx:rmi:///jndi/rmi://" + args[0] + "/jmxrmi";
136            }
137            JMXServiceURL url = new JMXServiceURL(serviceUrl);
138            Map<String, String> paramMap = new HashMap<String, String>();
139            for (Object objKey : System.getProperties().keySet()) {
140                String key = (String) objKey;
141                paramMap.put(key, System.getProperties().getProperty(key));
142            }
143            JMXConnector connector = JMXConnectorFactory.connect(url, paramMap);
144            final Client client = new Client(connector);
145            final String title = "Log4J2 JMX Client - " + url;
146    
147            SwingUtilities.invokeLater(new Runnable() {
148                @Override
149                public void run() {
150                    installLookAndFeel();
151                    try {
152                        ClientGUI gui = new ClientGUI(client);
153                        JFrame frame = new JFrame(title);
154                        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
155                        frame.getContentPane().add(gui, BorderLayout.CENTER);
156                        frame.pack();
157                        frame.setVisible(true);
158                    } catch (Exception ex) {
159                        // if console is visible, print error so that
160                        // the stack trace remains visible after error dialog is
161                        // closed
162                        ex.printStackTrace();
163    
164                        // show error in dialog: there may not be a console window
165                        // visible
166                        StringWriter sr = new StringWriter();
167                        ex.printStackTrace(new PrintWriter(sr));
168                        JOptionPane.showMessageDialog(null, sr.toString(), "Error",
169                                JOptionPane.ERROR_MESSAGE);
170                    }
171                }
172            });
173        }
174    
175        private static void usage() {
176            String me = ClientGUI.class.getName();
177            System.err.println("Usage: java " + me + " <host>:<port>");
178            System.err.println("   or: java " + me
179                    + " service:jmx:rmi:///jndi/rmi://<host>:<port>/jmxrmi");
180            String longAdr = " service:jmx:rmi://<host>:<port>/jndi/rmi://<host>:<port>/jmxrmi";
181            System.err.println("   or: java " + me + longAdr);
182        }
183    
184        private static void installLookAndFeel() {
185            try {
186                for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
187                    if ("Nimbus".equals(info.getName())) {
188                        UIManager.setLookAndFeel(info.getClassName());
189                        return;
190                    }
191                }
192            } catch (Exception ex) {
193                ex.printStackTrace();
194            }
195            try {
196                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
197            } catch (Exception e) {
198                e.printStackTrace();
199            }
200        }
201    }