In Service.java, the main() method accepts two arguments - start and -stop. The -start argument activates a loop which continually logs the current date/time to a log file called service.log. On the other hand, the -stop argument simply sets a stop flag to true at line 35. When the main program loop detects the stop flag, it will terminate (line 23-27).
File: Service.java
1: package examples;
2:
3: import java.io.*;
4: import java.util.*;
5:
6: public class Service
7: {
8: static boolean stop = false;
9: public static void main(String[] args) throws Exception
10: {
11: // Start the service
12: if (args[0].equals("-start"))
13: {
14: while(true)
15: {
16: // Append current date/time to log file
17: log("Current date/time is " + new Date());
18:
19: // Sleep for 5 secs
20: Thread.currentThread().sleep(5000);
21:
22: // Check for termination
23: if (stop)
24: {
25: log("Service stopped.");
26: break;
27: }
28: }
29: }
30: else
31: // Stop the service
32: if (args[0].equals("-stop"))
33: {
34: // Set the termination flag
35: stop = true;
36: }
37: }
38:
39: /**
40: * Log given string to file "service.log".
41: */
42: static void log(String msg) throws IOException
43: {
44: PrintWriter pw = new PrintWriter(
45: new FileWriter("service.log", true));
46: pw.println(msg);
47: pw.close();
48: }
49: }
This approach works with NativeJ-generated executables, but it will not work when moved to another platform such as Solaris, where shell scripts are the norm. This is because the -start and -stop arguments will be passed to different JVM instances, which means they will be executing in different address space.
A more platform-independent way of terminating a Java program that is meant to run continuously as a service (Win32) or daemon (Unix) is to implement some form of IPC (inter-process communication). An example is given in Service2.java, which uses IP datagrams to communicate the intent for program termination.
File: Service2.java
1: package examples;
2:
3: import java.io.*;
4: import java.net.*;
5: import java.util.*;
6:
7: public class Service2
8: {
9: /**
10: * This is the port over which the termination signal is sent.
11: */
12: private final static int port = 5678;
13:
14: /**
15: * This is the message to be sent to signal termination.
16: */
17: private final static String terminator = "QUIT";
19:
20: /**
21: * The main() function accepts one single parameter:
22: * "start" or "stop".
23: * The first parameter starts the date/time logging service,
24: * while the second parameter stops the service.
25: */
26: public static void main(String[] args) throws Exception
27: {
28: // Start the service
29: if (args[0].equals("start"))
30: {
31: try
32: {
33: // Run the termination listener thread
34: TerminationListener t = new TerminationListener(
35: port, terminator, Thread.currentThread());
36: t.start();
37:
38: // Start the logging service
39: while(true)
40: {
41: // Append current date/time to log file
42: log("Current date/time is " + new Date());
43:
44: // Sleep for 5 secs
45: Thread.currentThread().sleep(5000);
46: }
47: }
48: catch(InterruptedException e)
49: {
50: // Exit when thread is interrupted.
51: log("Service stopped.");
52: }
53: }
54: else
55: // Stop the service
56: if (args[0].equals("stop"))
57: {
58: try
59: {
60: // Send the termination message
61: DatagramSocket socket = new DatagramSocket();
62: DatagramPacket packet = new DatagramPacket(
63: terminator.getBytes(), terminator.length(),
64: InetAddress.getLocalHost(), port);
65: socket.send(packet);
66: }
67: catch(Exception e)
68: {
69: e.printStackTrace();
70: }
71: }
72: }
73:
74: /**
75: * Log given string to file "service.log".
76: */
77: static void log(String msg) throws IOException
78: {
79: PrintWriter pw = new PrintWriter(
80: new FileWriter("service.log", true));
81: pw.println(msg);
82: pw.close();
83: }
84: }
85:
86: /**
87: * This is a thread that will listen for the termination message
88: * over the designated port, then interrupt the parent thread.
89: */
90: class TerminationListener extends Thread
91: {
92: Thread parent;
93: int port;
94: String terminator;
95:
96: /**
97: * Set this thread to daemon mode so that if for some reason
98: * the main thread exists, this thread will not prevent the
99: * JVM from terminating.
100: */
101: public TerminationListener(
102: int port, String terminator, Thread parent)
103: {
104: setDaemon(true);
105: this.port = port;
106: this.terminator = terminator;
107: this.parent = parent;
108: }
109:
110: /**
111: * Listen for the termination signal over the designated port.
112: */
113: public void run()
114: {
115: try
116: {
117: // Setup datagram socket
118: DatagramSocket socket = new DatagramSocket(port);
119: DatagramPacket packet = new DatagramPacket(
120: new byte[terminator.length()], terminator.length());
121:
122: // Stop only when we have received the termination message
123: while(true)
124: {
125: // Wait for a message
126: socket.receive(packet);
127: String msg = new String(packet.getData());
128:
129: // Make sure the message is coming from the same
130: // machine. This is included for additional security
131: // so that the program cannot be terminated from an
132: // external machine).
133: if (!packet.getAddress().equals(
134: InetAddress.getLocalHost()))
135: continue;
136:
137: // Make sure the message is the termination message
138: if (!msg.equals(terminator)) continue;
139:
140: // Interrupt parent thread
141: parent.interrupt();
142:
143: // Terminate this thread
144: break;
145: }
146: }
147: catch(Exception e)
148: {
149: }
150: }
151:}
A TerminationListener class is defined in line 90, that runs in the background and waits for an IP datagram from a predefined port (default: 5678) on the local machine. This is instantiated and started in lines 34-36 when the -start argument is used. Let's say this runs in JVM instance #1.
When the -stop argument is issued, this runs in JVM instance #2. The instructions in lines 61-65 will send out an IP datagram containing the message "QUIT". This will be routed over to the TerminationListener in JVM instance #1, logging activities will stop, and JVM instance #1 will terminate. Similarly, once JVM instance #2 has sent out the "QUIT" message, it will also terminate.
This approach to graceful termination of a server app will work under NativeJ, as well as all Java-enabled platforms with TCP/IP capability. Hence, you will have one set of source codes that can work with both NativeJ-generated Win32 executables, as well as on other platforms using more traditional batch files, shell scripts, or just plain "java <class>".