001: package Shared.Logging;
002:
003: import java.util.ArrayList;
004: import java.awt.*;
005: import java.awt.event.*;
006: import javax.swing.*;
007: import javax.swing.event.*;
008:
009: import java.io.*;
010: import java.io.PrintStream;
011: import java.net.*;
012:
013: import javax.crypto.*;
014: import javax.crypto.spec.*;
015: import javax.crypto.interfaces.*;
016:
017: import Shared.Logging.Internal.CypherFactory;
018:
019: /**
020: * A standalone receiver for SocketLogger messages
021: * sent through sockets over the internet.
022: * It uses a file logger for saving the received
023: * log texts to disk.
024: */
025: public class SocketLogReceiver extends JFrame {
026:
027: private JTextField serverPortTextField;
028: private JLabel serverStateLabel;
029: private JButton startButton;
030: private JButton stopButton;
031:
032: private DataOutputStream dataOutputStream = null;
033:
034: private JTextArea textArea;
035:
036: private ListenerThread listenerThread;
037:
038: private Cipher decriptingCipher;
039:
040: /**
041: * List with the currently connected client connections.
042: */
043: private ArrayList<ClientConnection> clientConnections = new ArrayList<ClientConnection>();
044: private JLabel clientCountLabel;
045:
046: public SocketLogReceiver() {
047: this .setTitle("SocketLogReceiver");
048: Log.Info("starts.");
049: try {
050: this .decriptingCipher = CypherFactory
051: .CreatePasswordBasedCipher(Cipher.DECRYPT_MODE);
052: } catch (Exception e) {
053: EventQueue.invokeLater(new Runnable() {
054: public void run() {
055: JOptionPane.showMessageDialog(
056: SocketLogReceiver.this ,
057: "Unable to create cipher. Shutting down.",
058: "Error", JOptionPane.ERROR_MESSAGE);
059: Log.Info("SocketListener shuts down.");
060: System.exit(0);
061: }
062: });
063: }
064:
065: this .getContentPane().setLayout(new BorderLayout());
066:
067: JPanel actionPanel = new JPanel(new FlowLayout());
068: this .serverPortTextField = new JTextField("0", 4);
069: this .serverStateLabel = new JLabel("Enter a portnumber.");
070: this .startButton = new JButton("Start");
071: startButton.addActionListener(new ActionListener() {
072: public void actionPerformed(ActionEvent e) {
073: startListening();
074: }
075: });
076: this .stopButton = new JButton("Stop");
077: this .stopButton.setEnabled(false);
078: stopButton.addActionListener(new ActionListener() {
079: public void actionPerformed(ActionEvent e) {
080: stopListening();
081: }
082: });
083: this .clientCountLabel = new JLabel(" # clients: 0");
084: actionPanel.add(this .serverStateLabel);
085: actionPanel.add(this .serverPortTextField);
086: actionPanel.add(this .startButton);
087: actionPanel.add(this .stopButton);
088: actionPanel.add(this .clientCountLabel);
089:
090: this .getContentPane().add(actionPanel, BorderLayout.NORTH);
091:
092: this .textArea = new JTextArea();
093: JScrollPane textScrollPane = this
094: .createScrollPaneFor(this .textArea);
095: this .getContentPane().add(textScrollPane, BorderLayout.CENTER);
096:
097: JButton exitButton = new JButton(" Close ");
098: exitButton.addActionListener(new ActionListener() {
099: public void actionPerformed(ActionEvent e) {
100: stopListening();
101: setVisible(false);
102: Log.Info("SocketListener shuts down.");
103: System.exit(0);
104: }
105: });
106: JPanel buttonPanel = new JPanel(new FlowLayout());
107: buttonPanel.add(exitButton);
108: this .getContentPane().add(buttonPanel, BorderLayout.SOUTH);
109:
110: // Get the screen rect, which is usable (without area consumed by native toolbars):
111: Rectangle usableScreenRect = GraphicsEnvironment
112: .getLocalGraphicsEnvironment().getMaximumWindowBounds();
113: int hInset = usableScreenRect.width / 4;
114: int vInset = usableScreenRect.height / 4;
115: usableScreenRect.grow(-hInset, -vInset);
116:
117: this .setLocation(usableScreenRect.x, usableScreenRect.y);
118: this .setSize(usableScreenRect.width, usableScreenRect.height);
119: } // Constructor
120:
121: public void startListening() {
122: int portNumber = -1;
123: try {
124: portNumber = Integer.parseInt(this .serverPortTextField
125: .getText());
126: } catch (Exception e) {
127: this .serverPortTextField.setText("0");
128: }
129: if (portNumber >= 0) {
130: this .startButton.setEnabled(false);
131: this .stopButton.setEnabled(true);
132: this .serverStateLabel.setText("Listening...");
133:
134: // Do not use the logger for output - it is used for showing the received data
135: Log.Info(">Listening on port " + portNumber);
136: this .textArea.append(">Listening on port " + portNumber
137: + "\n");
138: this .listenerThread = new ListenerThread(portNumber);
139: this .listenerThread.start();
140:
141: } else {
142: // Do not use the logger for output - it is used for showing the received data
143: Log.Info(">Incorrect portnumber, please enter a number.");
144: this .textArea
145: .append(">Incorrect portnumber, please enter a number.\n");
146: }
147: }
148:
149: public void stopListening() {
150: this .startButton.setEnabled(true);
151: this .stopButton.setEnabled(false);
152: this .serverStateLabel.setText("Enter a portnumber.");
153: if (this .listenerThread != null) {
154: Log.Info(">Listening stopped");
155: this .textArea.append(">Listening stopped\n");
156: this .listenerThread.terminate();
157: this .listenerThread = null;
158: }
159: }
160:
161: private JScrollPane createScrollPaneFor(final JComponent component) {
162: JScrollPane scrollPane = new JScrollPane(component);
163: final int fontSize = UIManager.getFont("TextField.font")
164: .getSize();
165: final int unitIncrement = fontSize;
166: final int blockIncrement = 4 * fontSize;
167: scrollPane.getVerticalScrollBar().setUnitIncrement(
168: unitIncrement);
169: scrollPane.getVerticalScrollBar().setBlockIncrement(
170: blockIncrement);
171: return scrollPane;
172: }
173:
174: private class ListenerThread extends Thread {
175:
176: private final int socketTimeout = 40000; // 40 second timeout for sockets
177: private final int serverSocketTimeout = 444; // 444 ms timeout for the server socket
178: private ServerSocket serverSocket;
179:
180: private boolean doTerminate = false;
181:
182: public ListenerThread(int portNumber) {
183: this .setDaemon(true); // daemon thread
184: try {
185: this .serverSocket = new ServerSocket(portNumber);
186: this .serverSocket.setSoTimeout(serverSocketTimeout);
187: } catch (BindException be) {
188: // this happens, when another socket already is listening
189: // on this port. One has to stop and shutdown.
190: be.printStackTrace();
191: this .doTerminate = true;
192: EventQueue.invokeLater(new Runnable() {
193: public void run() {
194: JOptionPane
195: .showMessageDialog(
196: SocketLogReceiver.this ,
197: "Another socket already is listening on this port. Shutting down.",
198: "Error",
199: JOptionPane.ERROR_MESSAGE);
200: Log.Info("SocketListener shuts down.");
201: System.exit(0);
202: }
203: });
204: } catch (Exception ex) {
205: this .doTerminate = true;
206: ex.printStackTrace();
207: }
208: }
209:
210: public void run() {
211: Log.Info("starts");
212: while (!this .doTerminate) {
213: try {
214: Socket clientSocket = this .serverSocket.accept();
215: if (clientSocket != null) {
216: ClientConnection c = new ClientConnection(
217: clientSocket);
218: int nc = 0;
219: synchronized (clientConnections) {
220: clientConnections.add(c);
221: nc = clientConnections.size();
222: }
223: final int fnc = nc;
224: c.start();
225: EventQueue.invokeLater(new Runnable() {
226: public void run() {
227: clientCountLabel.setText(" # clients: "
228: + fnc);
229: }
230: });
231: }
232: } catch (SocketTimeoutException timeOutException) {
233: // this happens all 444 ms as normal operation
234: } catch (SocketException se) {
235: // this happens, when the socket is closed by stopListening()
236: this .doTerminate = true;
237: } catch (Exception ex) {
238: ex.printStackTrace();
239: this .doTerminate = true;
240: } catch (Error err) {
241: err.printStackTrace();
242: this .doTerminate = true;
243: }
244: }
245: // release the connections, close the sockets:
246: for (int i = 0; i < clientConnections.size(); i++) {
247: clientConnections.get(i).terminateConnection();
248: }
249: Log.Info("stopped.");
250: }
251:
252: public void terminate() {
253: this .doTerminate = true;
254: this .interrupt();
255: try {
256: this .serverSocket.close();
257: } catch (Exception e) {
258: e.printStackTrace();
259: }
260: }
261:
262: } // inner class ListenerThread
263:
264: private class ClientConnection extends Thread {
265:
266: private Socket clientSocket;
267: private DataInputStream dataInputStream;
268: private boolean doTerminate = false;
269: private String clientIP = "unknown";
270:
271: public ClientConnection(Socket _clientSocket) {
272: this .setDaemon(true); // daemon thread
273: try {
274: this .clientSocket = _clientSocket;
275: this .dataInputStream = new DataInputStream(
276: this .clientSocket.getInputStream());
277: this .clientIP = this .clientSocket.getInetAddress()
278: .toString();
279: } catch (Exception ioe) {
280: ioe.printStackTrace();
281: }
282: }
283:
284: public void run() {
285: try {
286: // Now read the identification data:
287: String identification = this .readAndDecryptText();
288: // Close this connection, if it's not the correct identification:
289: if ((identification != null)
290: && (identification
291: .equals(Log.SocketLoggerIdentification))) {
292: // Identification correct, so start listening:
293: EventQueue.invokeLater(new Runnable() {
294: public void run() {
295: Log.Info("Client has connected.");
296: Log.Info("Connection established to "
297: + clientIP);
298: textArea.append(">Client has connected.\n");
299: textArea
300: .append(">Connection established to "
301: + clientIP + "\n");
302: }
303: });
304: while (!this .doTerminate) {
305: final String logText = this
306: .readAndDecryptText();
307: // pass the received logText without any further formatting
308: // to the loggers and the text area:
309: Log.DirectOutput(logText);
310: EventQueue.invokeLater(new Runnable() {
311: public void run() {
312: appendText(logText);
313: appendText("\n");
314: }
315: });
316: }
317: }
318: } catch (Exception ex) {
319: // client has closed the socket
320: } catch (Error err) {
321: // client has closed the socket
322: } finally {
323: int numberOfConnectedClients = 0;
324: synchronized (clientConnections) {
325: // remove this thread from the list
326: clientConnections.remove(this );
327: numberOfConnectedClients = clientConnections.size();
328: }
329: final int finalNumberOfConnectedClients = numberOfConnectedClients;
330: EventQueue.invokeLater(new Runnable() {
331: public void run() {
332: Log
333: .Info("Client has disconnected. Connection closed to "
334: + clientIP);
335: Log.Info("Number of connected clients: "
336: + finalNumberOfConnectedClients);
337: textArea
338: .append(">Client has disconnected. Connection closed to "
339: + clientIP + "\n");
340: textArea
341: .append(">Number of connected clients: "
342: + finalNumberOfConnectedClients
343: + "\n");
344: clientCountLabel.setText(" # clients: "
345: + finalNumberOfConnectedClients);
346: }
347: });
348: try {
349: this .clientSocket.close();
350: this .interrupt();
351: this .clientSocket = null;
352: this .dataInputStream = null;
353: } catch (Exception e) {
354: // nothing
355: }
356: }
357: }
358:
359: private String readAndDecryptText() {
360: String clearText = null;
361: try {
362: // read number of encrypted bytes to follow:
363: int numberOfCipheredBytes = this .dataInputStream
364: .readInt();
365: // read them:
366: byte[] cipheredBytes = new byte[numberOfCipheredBytes];
367: for (int i = 0; i < numberOfCipheredBytes; i++) {
368: cipheredBytes[i] = this .dataInputStream.readByte();
369: }
370: // decript:
371: byte[] clearBytes = decriptingCipher
372: .doFinal(cipheredBytes);
373: // Note: The sending socket logger always uses the UTF-8 charset,
374: // so one has to use this one here too:
375: clearText = new String(clearBytes, "UTF-8");
376: } catch (Exception e) {
377: textArea
378: .append("Exception in the SocketLogReceiver.readAndDecryptText().\n");
379: textArea
380: .append("The client has closed the connection.\n");
381: Log.Error(e);
382: }
383: return clearText;
384: }
385:
386: private void terminateConnection() {
387: this .doTerminate = true;
388: try {
389: this .clientSocket.close();
390: } catch (Exception e) {
391: }
392: }
393:
394: } // ClientConnection
395:
396: /**
397: * Appends text to the textArea, and
398: * removes text at the beginning, if the
399: * text content gets too big. (Size limitation)
400: */
401: private int textCounter = 0;
402:
403: private void appendText(final String text) {
404: this .textArea.append(text);
405: textCounter++;
406: // Don't test the text length always, but just each 20st time:
407: if (textCounter > 20) {
408: textCounter = 0; // reset
409: String areaText = this .textArea.getText();
410: if (areaText.length() > 60000) {
411: int index = 0;
412: int numberOfNewLines = 0;
413: while ((index < areaText.length() / 2)
414: && (numberOfNewLines < 50)) {
415: if (areaText.charAt(index) == '\n') {
416: numberOfNewLines++;
417: }
418: index++;
419: }
420: textArea.replaceRange("", 0, index); // remove text up to this index
421: }
422: }
423: }
424:
425: public static void main(String[] arguments) {
426: // Configure the Log, before it is used the first time:
427: Log.SetNumberOfFiles(100); // 100 files ( 1 MB each is default already )
428: Log.SetBasisFileName("receiverlog");
429: final SocketLogReceiver receiver = new SocketLogReceiver();
430: receiver.setVisible(true);
431: }
432:
433: } // SocketLogReceiver
|