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