1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.log4j.chainsaw.zeroconf;
18
19 import java.awt.BorderLayout;
20 import java.awt.Component;
21 import java.awt.event.ActionEvent;
22 import java.awt.event.MouseAdapter;
23 import java.awt.event.MouseEvent;
24 import java.io.File;
25 import java.io.FileReader;
26 import java.io.FileWriter;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.Map;
31 import java.util.Set;
32
33 import javax.jmdns.JmDNS;
34 import javax.jmdns.ServiceEvent;
35 import javax.jmdns.ServiceInfo;
36 import javax.jmdns.ServiceListener;
37 import javax.swing.AbstractAction;
38 import javax.swing.ImageIcon;
39 import javax.swing.JFrame;
40 import javax.swing.JMenu;
41 import javax.swing.JMenuBar;
42 import javax.swing.JMenuItem;
43 import javax.swing.JPopupMenu;
44 import javax.swing.JScrollPane;
45 import javax.swing.JTable;
46 import javax.swing.JToolBar;
47 import javax.swing.SwingUtilities;
48 import javax.swing.event.TableModelEvent;
49 import javax.swing.event.TableModelListener;
50
51 import org.apache.log4j.BasicConfigurator;
52 import org.apache.log4j.LogManager;
53 import org.apache.log4j.Logger;
54 import org.apache.log4j.chainsaw.ChainsawConstants;
55 import org.apache.log4j.chainsaw.LogFilePatternLayoutBuilder;
56 import org.apache.log4j.chainsaw.SmallButton;
57 import org.apache.log4j.chainsaw.help.HelpManager;
58 import org.apache.log4j.chainsaw.icons.ChainsawIcons;
59 import org.apache.log4j.chainsaw.plugins.GUIPluginSkeleton;
60 import org.apache.log4j.chainsaw.prefs.SettingsManager;
61 import org.apache.log4j.chainsaw.vfs.VFSLogFilePatternReceiver;
62 import org.apache.log4j.helpers.LogLog;
63 import org.apache.log4j.net.MulticastReceiver;
64 import org.apache.log4j.net.SocketHubReceiver;
65 import org.apache.log4j.net.SocketReceiver;
66 import org.apache.log4j.net.UDPReceiver;
67 import org.apache.log4j.net.XMLSocketReceiver;
68 import org.apache.log4j.net.ZeroConfSupport;
69 import org.apache.log4j.plugins.Plugin;
70 import org.apache.log4j.plugins.PluginEvent;
71 import org.apache.log4j.plugins.PluginListener;
72 import org.apache.log4j.plugins.Receiver;
73 import org.apache.log4j.spi.LoggerRepositoryEx;
74
75 import com.thoughtworks.xstream.XStream;
76 import com.thoughtworks.xstream.io.xml.DomDriver;
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 public class ZeroConfPlugin extends GUIPluginSkeleton {
92
93 private static final Logger LOG = Logger.getLogger(ZeroConfPlugin.class);
94
95 private ZeroConfDeviceModel discoveredDevices = new ZeroConfDeviceModel();
96
97 private JTable deviceTable = new JTable(discoveredDevices);
98
99 private final JScrollPane scrollPane = new JScrollPane(deviceTable);
100
101 private ZeroConfPreferenceModel preferenceModel;
102
103 private Map serviceInfoToReceiveMap = new HashMap();
104
105 private JMenu connectToMenu = new JMenu("Connect to");
106 private JMenuItem helpItem = new JMenuItem(new AbstractAction("Learn more about ZeroConf...",
107 ChainsawIcons.ICON_HELP) {
108
109 public void actionPerformed(ActionEvent e) {
110 HelpManager.getInstance()
111 .showHelpForClass(ZeroConfPlugin.class);
112 }
113 });
114
115 private JMenuItem nothingToConnectTo = new JMenuItem("No devices discovered");
116 private static final String MULTICAST_APPENDER_SERVICE_NAME = "_log4j_xml_mcast_appender.local.";
117 private static final String UDP_APPENDER_SERVICE_NAME = "_log4j_xml_udp_appender.local.";
118 private static final String XML_SOCKET_APPENDER_SERVICE_NAME = "_log4j_xml_tcpconnect_appender.local.";
119 private static final String SOCKET_APPENDER_SERVICE_NAME = "_log4j_obj_tcpconnect_appender.local.";
120 private static final String SOCKETHUB_APPENDER_SERVICE_NAME = "_log4j_obj_tcpaccept_appender.local.";
121 private static final String TCP_APPENDER_SERVICE_NAME = "_log4j._tcp.local.";
122 private static final String NEW_UDP_APPENDER_SERVICE_NAME = "_log4j._udp.local.";
123
124 private JmDNS jmDNS;
125
126 public ZeroConfPlugin() {
127 setName("Zeroconf");
128 deviceTable.setRowHeight(ChainsawConstants.DEFAULT_ROW_HEIGHT);
129 }
130
131 public void shutdown() {
132 if (jmDNS != null) {
133 try {
134 jmDNS.close();
135 } catch (Exception e) {
136 LOG.error("Unable to close JMDNS", e);
137 }
138 }
139 save();
140 }
141
142 private void save() {
143 File fileLocation = getPreferenceFileLocation();
144 XStream stream = new XStream(new DomDriver());
145 try {
146 stream.toXML(preferenceModel, new FileWriter(fileLocation));
147 } catch (Exception e) {
148 LOG.error("Failed to save ZeroConfPlugin configuration file",e);
149 }
150 }
151
152 private File getPreferenceFileLocation() {
153 return new File(SettingsManager.getInstance().getSettingsDirectory(), "zeroconfprefs.xml");
154 }
155
156 public void activateOptions() {
157 setLayout(new BorderLayout());
158 jmDNS = (JmDNS) ZeroConfSupport.getJMDNSInstance();
159
160 registerServiceListenersForAppenders();
161
162 deviceTable.addMouseListener(new ConnectorMouseListener());
163
164
165 JToolBar toolbar = new JToolBar();
166 SmallButton helpButton = new SmallButton(helpItem.getAction());
167 helpButton.setText(helpItem.getText());
168 toolbar.add(helpButton);
169 toolbar.setFloatable(false);
170 add(toolbar, BorderLayout.NORTH);
171 add(scrollPane, BorderLayout.CENTER);
172
173 injectMenu();
174
175 ((LoggerRepositoryEx)LogManager.getLoggerRepository()).getPluginRegistry().addPluginListener(new PluginListener() {
176
177 public void pluginStarted(PluginEvent e) {
178
179 }
180
181 public void pluginStopped(PluginEvent e) {
182 Plugin plugin = e.getPlugin();
183 synchronized(serviceInfoToReceiveMap) {
184 for (Iterator iter = serviceInfoToReceiveMap.entrySet().iterator(); iter.hasNext();) {
185 Map.Entry entry = (Map.Entry) iter.next();
186 if(entry.getValue() == plugin) {
187 iter.remove();
188 }
189 }
190 }
191
192 discoveredDevices.fireTableDataChanged();
193 }});
194
195 File fileLocation = getPreferenceFileLocation();
196 XStream stream = new XStream(new DomDriver());
197 if (fileLocation.exists()) {
198 try {
199 this.preferenceModel = (ZeroConfPreferenceModel) stream
200 .fromXML(new FileReader(fileLocation));
201 } catch (Exception e) {
202 LOG.error("Failed to load ZeroConfPlugin configuration file",e);
203 }
204 }else {
205 this.preferenceModel = new ZeroConfPreferenceModel();
206 }
207 discoveredDevices.setZeroConfPreferenceModel(preferenceModel);
208 discoveredDevices.setZeroConfPluginParent(this);
209 }
210
211 private void registerServiceListenersForAppenders()
212 {
213 Set serviceNames = new HashSet();
214 serviceNames.add(MULTICAST_APPENDER_SERVICE_NAME);
215 serviceNames.add(SOCKET_APPENDER_SERVICE_NAME);
216 serviceNames.add(SOCKETHUB_APPENDER_SERVICE_NAME);
217 serviceNames.add(UDP_APPENDER_SERVICE_NAME);
218 serviceNames.add(XML_SOCKET_APPENDER_SERVICE_NAME);
219 serviceNames.add(TCP_APPENDER_SERVICE_NAME);
220 serviceNames.add(NEW_UDP_APPENDER_SERVICE_NAME);
221
222 for (Iterator iter = serviceNames.iterator(); iter.hasNext();) {
223 String serviceName = iter.next().toString();
224 jmDNS.addServiceListener(
225 serviceName,
226 new ZeroConfServiceListener());
227
228 jmDNS.addServiceListener(serviceName, discoveredDevices);
229 }
230
231
232 }
233
234
235
236
237
238 private void injectMenu() {
239
240 JFrame frame = (JFrame) SwingUtilities.getWindowAncestor(this);
241 if(frame == null) {
242 LOG.info("Could not locate parent JFrame to add menu to");
243 }else {
244 JMenuBar menuBar = frame.getJMenuBar();
245 if(menuBar==null ) {
246 menuBar = new JMenuBar();
247 frame.setJMenuBar(menuBar);
248 }
249 insertToLeftOfHelp(menuBar, connectToMenu);
250 connectToMenu.add(nothingToConnectTo);
251
252 discoveredDevices.addTableModelListener(new TableModelListener (){
253
254 public void tableChanged(TableModelEvent e) {
255 if(discoveredDevices.getRowCount()==0) {
256 connectToMenu.add(nothingToConnectTo,0);
257 }else if(discoveredDevices.getRowCount()>0) {
258 connectToMenu.remove(nothingToConnectTo);
259 }
260
261 }});
262
263 nothingToConnectTo.setEnabled(false);
264
265 connectToMenu.addSeparator();
266 connectToMenu.add(helpItem);
267 }
268 }
269
270
271
272
273
274
275
276 private void insertToLeftOfHelp(JMenuBar menuBar, JMenu item) {
277 for (int i = 0; i < menuBar.getMenuCount(); i++) {
278 JMenu menu = menuBar.getMenu(i);
279 if(menu.getText().equalsIgnoreCase("help")) {
280 menuBar.add(item, i-1);
281 }
282 }
283 LOG.warn("menu '" + item.getText() + "' was NOT added because the 'Help' menu could not be located");
284 }
285
286
287
288
289
290
291
292 private void deviceDiscovered(final ServiceInfo info) {
293 final String name = info.getName();
294
295
296
297
298 JMenuItem connectToDeviceMenuItem = new JMenuItem(new AbstractAction(info.getName()) {
299
300 public void actionPerformed(ActionEvent e) {
301 connectTo(info);
302 }});
303
304 if(discoveredDevices.getRowCount()>0) {
305 Component[] menuComponents = connectToMenu.getMenuComponents();
306 boolean located = false;
307 for (int i = 0; i < menuComponents.length; i++) {
308 Component c = menuComponents[i];
309 if (!(c instanceof JPopupMenu.Separator)) {
310 JMenuItem item = (JMenuItem) menuComponents[i];
311 if (item.getText().compareToIgnoreCase(name) < 0) {
312 connectToMenu.insert(connectToDeviceMenuItem, i);
313 located = true;
314 break;
315 }
316 }
317 }
318 if(!located) {
319 connectToMenu.insert(connectToDeviceMenuItem,0);
320 }
321 }else {
322 connectToMenu.insert(connectToDeviceMenuItem,0);
323 }
324
325 if (preferenceModel != null && preferenceModel.getAutoConnectDevices() != null && preferenceModel.getAutoConnectDevices().contains(name)) {
326 new Thread(new Runnable() {
327
328 public void run() {
329 LOG.info("Auto-connecting to " + name);
330 connectTo(info);
331 }
332 }).start();
333 }
334 }
335
336
337
338
339
340 private void deviceRemoved(String name) {
341 Component[] menuComponents = connectToMenu.getMenuComponents();
342 for (int i = 0; i < menuComponents.length; i++) {
343 Component c = menuComponents[i];
344 if (!(c instanceof JPopupMenu.Separator)) {
345 JMenuItem item = (JMenuItem) menuComponents[i];
346 if (item.getText().compareToIgnoreCase(name) == 0) {
347 connectToMenu.remove(item);
348 break;
349 }
350 }
351 }
352 }
353
354
355
356
357
358
359 private class ZeroConfServiceListener implements ServiceListener {
360
361 public void serviceAdded(final ServiceEvent event) {
362 LOG.info("Service Added: " + event);
363
364
365
366
367
368 Runnable runnable = new Runnable() {
369 public void run() {
370 ZeroConfPlugin.this.jmDNS.requestServiceInfo(event
371 .getType(), event.getName());
372 }
373 };
374 Thread thread = new Thread(runnable,
375 "ChainsawZeroConfRequestResolutionThread");
376 thread.setPriority(Thread.MIN_PRIORITY);
377 thread.start();
378 }
379
380 public void serviceRemoved(ServiceEvent event) {
381 LOG.info("Service Removed: " + event);
382 deviceRemoved(event.getName());
383 }
384
385 public void serviceResolved(ServiceEvent event) {
386 LOG.info("Service Resolved: " + event);
387 deviceDiscovered(event.getInfo());
388 }
389
390 }
391
392
393
394
395
396
397 private class ConnectorMouseListener extends MouseAdapter {
398
399 public void mouseClicked(MouseEvent e) {
400 if (e.getClickCount() == 2) {
401 int row = deviceTable.rowAtPoint(e.getPoint());
402 if(deviceTable.columnAtPoint(e.getPoint())==2) {
403 return;
404 }
405 ServiceInfo info = discoveredDevices.getServiceInfoAtRow(row);
406
407 if (!isConnectedTo(info)) {
408 connectTo(info);
409 } else {
410 disconnectFrom(info);
411 }
412 }
413 }
414
415 public void mousePressed(MouseEvent e) {
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438 }
439 }
440
441 private void disconnectFrom(ServiceInfo info) {
442 if(!isConnectedTo(info)) {
443 return;
444 }
445 Plugin plugin;
446 synchronized (serviceInfoToReceiveMap) {
447 plugin = (Plugin) serviceInfoToReceiveMap.get(info);
448 }
449 ((LoggerRepositoryEx)LogManager.getLoggerRepository()).getPluginRegistry().stopPlugin(plugin.getName());
450
451 JMenuItem item = locateMatchingMenuItem(info.getName());
452 if (item!=null) {
453 item.setIcon(null);
454 item.setEnabled(true);
455 }
456 }
457
458
459
460
461
462 boolean isConnectedTo(ServiceInfo info) {
463 return serviceInfoToReceiveMap.containsKey(info);
464 }
465
466
467
468
469 private void connectTo(ServiceInfo info) {
470 LOG.info("Connection request for " + info);
471
472 Receiver receiver = getReceiver(info);
473
474 if (receiver == null) {
475 return;
476 }
477 ((LoggerRepositoryEx)LogManager.getLoggerRepository()).getPluginRegistry().addPlugin(receiver);
478 receiver.activateOptions();
479 LOG.info("Receiver '" + receiver.getName() + "' has been started");
480
481
482 synchronized (serviceInfoToReceiveMap) {
483 serviceInfoToReceiveMap.put(info, receiver);
484 }
485
486
487 JMenuItem item = locateMatchingMenuItem(info.getName());
488 if (item!=null) {
489 item.setIcon(new ImageIcon(ChainsawIcons.ANIM_NET_CONNECT));
490 item.setEnabled(false);
491 }
492
493
494 }
495
496 private Receiver getReceiver(ServiceInfo info) {
497 String zone = info.getType();
498 int port = info.getPort();
499 String hostAddress = info.getHostAddress();
500 String name = info.getName();
501 String decoderClass = info.getPropertyString("decoder");
502
503 if (NEW_UDP_APPENDER_SERVICE_NAME.equals(zone))
504 {
505 UDPReceiver receiver = new UDPReceiver();
506 receiver.setPort(port);
507 receiver.setName(name + "-receiver");
508 return receiver;
509 }
510
511
512 if (TCP_APPENDER_SERVICE_NAME.equals(zone)) {
513
514
515
516 String contentType = info.getPropertyString("contentType").toLowerCase();
517
518 if ("application/octet-stream".equals(contentType))
519 {
520 SocketReceiver receiver = new SocketReceiver();
521 receiver.setPort(port);
522 receiver.setName(name + "-receiver");
523 return receiver;
524 }
525
526 if ("text/plain".equals(contentType))
527 {
528 VFSLogFilePatternReceiver receiver = new VFSLogFilePatternReceiver();
529 receiver.setAppendNonMatches(true);
530 receiver.setFileURL(info.getPropertyString("fileURI"));
531 receiver.setLogFormat(LogFilePatternLayoutBuilder.getLogFormatFromPatternLayout(info.getPropertyString("format")));
532 receiver.setTimestampFormat(LogFilePatternLayoutBuilder.getTimeStampFormat(info.getPropertyString("format")));
533 receiver.setName(name + "-receiver");
534 receiver.setTailing(true);
535 return receiver;
536 }
537 }
538
539
540 if (MULTICAST_APPENDER_SERVICE_NAME.equals(zone)) {
541 MulticastReceiver receiver = new MulticastReceiver();
542
543 receiver.setAddress(info.getPropertyString("multicastAddress"));
544 receiver.setPort(port);
545 receiver.setName(name + "-receiver");
546 if (decoderClass != null && !decoderClass.equals("")) {
547 receiver.setDecoder(decoderClass);
548 }
549
550 return receiver;
551 }
552
553 if (UDP_APPENDER_SERVICE_NAME.equals(zone)) {
554 UDPReceiver receiver = new UDPReceiver();
555 receiver.setPort(port);
556 receiver.setName(name + "-receiver");
557 if (decoderClass != null && !decoderClass.equals("")) {
558 receiver.setDecoder(decoderClass);
559 }
560 return receiver;
561 }
562
563
564 if (XML_SOCKET_APPENDER_SERVICE_NAME.equals(zone)) {
565 XMLSocketReceiver receiver = new XMLSocketReceiver();
566 receiver.setPort(port);
567 receiver.setName(name + "-receiver");
568 if (decoderClass != null && !decoderClass.equals("")) {
569 receiver.setDecoder(decoderClass);
570 }
571 return receiver;
572 }
573
574
575 if (SOCKET_APPENDER_SERVICE_NAME.equals(zone)) {
576 SocketReceiver receiver = new SocketReceiver();
577 receiver.setPort(port);
578 receiver.setName(name + "-receiver");
579 return receiver;
580 }
581
582
583 if (SOCKETHUB_APPENDER_SERVICE_NAME.equals(zone)) {
584 SocketHubReceiver receiver = new SocketHubReceiver();
585 receiver.setHost(hostAddress);
586 receiver.setPort(port);
587 receiver.setName(name + "-receiver");
588 return receiver;
589 }
590
591 LogLog.debug("Unable to find receiver for appender with service name: " + zone);
592 return null;
593 }
594
595
596
597
598
599
600
601 private JMenuItem locateMatchingMenuItem(String name) {
602 Component[] menuComponents = connectToMenu.getMenuComponents();
603 for (int i = 0; i < menuComponents.length; i++) {
604 Component c = menuComponents[i];
605 if (!(c instanceof JPopupMenu.Separator)) {
606 JMenuItem item = (JMenuItem) menuComponents[i];
607 if (item.getText().compareToIgnoreCase(name) == 0) {
608 return item;
609 }
610 }
611 }
612 return null;
613 }
614
615 public static void main(String[] args) throws InterruptedException {
616
617 BasicConfigurator.resetConfiguration();
618 BasicConfigurator.configure();
619
620 final ZeroConfPlugin plugin = new ZeroConfPlugin();
621
622
623 JFrame frame = new JFrame();
624 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
625
626 frame.getContentPane().setLayout(new BorderLayout());
627 frame.getContentPane().add(plugin, BorderLayout.CENTER);
628
629
630 plugin.activateOptions();
631
632 frame.pack();
633 frame.setVisible(true);
634
635 Thread thread = new Thread(new Runnable() {
636 public void run() {
637 plugin.shutdown();
638 }
639 });
640 Runtime.getRuntime().addShutdownHook(thread);
641 }
642
643 }