View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.commons.daemon;
19  
20  import java.io.*;
21  import java.net.*;
22  import java.text.SimpleDateFormat;
23  import java.util.Date;
24  import java.util.Enumeration;
25  import java.util.Vector;
26  
27  public class SimpleDaemon implements Daemon, Runnable, DaemonUserSignal {
28  
29      private ServerSocket server;
30      private Thread thread;
31      private DaemonController controller;
32      private volatile boolean stopping;
33      private String directory;
34      private final Vector<Handler> handlers;
35      private boolean softReloadSignalled;
36  
37      public SimpleDaemon() {
38          System.err.println("SimpleDaemon: instance " + hashCode() + " created");
39          this.handlers = new Vector<>();
40      }
41  
42      @Override
43      protected void finalize() {
44          System.err.println("SimpleDaemon: instance " + hashCode() + " garbage collected");
45      }
46  
47      /**
48       * init and destroy were added in jakarta-tomcat-daemon.
49       */
50      @Override
51      public void init(final DaemonContext context) throws Exception {
52          System.err.println("SimpleDaemon: instance " + hashCode() + " init");
53  
54          int port = 1200;
55  
56          final String[] a = context.getArguments();
57  
58          if (a.length > 0) {
59              port = Integer.parseInt(a[0]);
60          }
61          if (a.length > 1) {
62              this.directory = a[1];
63          } else {
64              this.directory = "/tmp";
65          }
66  
67          // Dump a message
68          System.err.println("SimpleDaemon: loading on port " + port);
69  
70          // Set up this simple daemon
71          this.controller = context.getController();
72          this.server = new ServerSocket(port);
73          this.thread = new Thread(this);
74      }
75  
76      @Override
77      public void start() {
78          // Dump a message
79          System.err.println("SimpleDaemon: starting");
80  
81          // Start
82          this.thread.start();
83      }
84  
85      @Override
86      public void stop() throws IOException, InterruptedException {
87          // Dump a message
88          System.err.println("SimpleDaemon: stopping");
89  
90          // Close the ServerSocket. This will make our thread to terminate
91          this.stopping = true;
92          this.server.close();
93  
94          // Wait for the main thread to exit and dump a message
95          this.thread.join(5000);
96          System.err.println("SimpleDaemon: stopped");
97      }
98  
99      @Override
100     public void destroy() {
101         System.err.println("SimpleDaemon: instance " + hashCode() + " destroy");
102     }
103 
104     @Override
105     public void run() {
106         int number = 0;
107 
108         System.err.println("SimpleDaemon: started acceptor loop");
109         try {
110             while (!this.stopping) {
111                 checkForReload();
112                 final Socket socket = this.server.accept();
113                 checkForReload();
114 
115                 final Handler handler = new Handler(socket, this, this.controller);
116                 handler.setConnectionNumber(number++);
117                 handler.setDirectoryName(this.directory);
118                 new Thread(handler).start();
119             }
120         } catch (final IOException e) {
121             //
122             // Don't dump any error message if we are stopping. A IOException is generated when the ServerSocket is closed in stop()
123             //
124             if (!this.stopping) {
125                 e.printStackTrace(System.err);
126             }
127         }
128 
129         // Terminate all handlers that at this point are still open
130         final Enumeration<Handler> openhandlers = this.handlers.elements();
131         while (openhandlers.hasMoreElements()) {
132             final Handler handler = openhandlers.nextElement();
133             System.err.println("SimpleDaemon: dropping connection " + handler.getConnectionNumber());
134             handler.close();
135         }
136 
137         System.err.println("SimpleDaemon: exiting acceptor loop");
138     }
139 
140     @Override
141     public void signal() {
142         //
143         // In this example we are using soft reload on custom signal.
144         //
145         this.softReloadSignalled = true;
146     }
147 
148     private void checkForReload() {
149         if (this.softReloadSignalled) {
150             System.err.println("SimpleDaemon: picked up reload, waiting for connections to finish...");
151             while (!this.handlers.isEmpty()) {
152                 // noop
153             }
154             System.err.println("SimpleDaemon: all connections have finished, pretending to reload");
155             this.softReloadSignalled = false;
156         }
157     }
158 
159     protected void addHandler(final Handler handler) {
160         synchronized (handler) {
161             this.handlers.add(handler);
162         }
163     }
164 
165     protected void removeHandler(final Handler handler) {
166         synchronized (handler) {
167             this.handlers.remove(handler);
168         }
169     }
170 
171     public static class Handler implements Runnable {
172 
173         private final DaemonController controller;
174         private final SimpleDaemon parent;
175         private String directory;
176         private final Socket socket;
177         private int number;
178 
179         public Handler(final Socket s, final SimpleDaemon p, final DaemonController c) {
180             this.socket = s;
181             this.parent = p;
182             this.controller = c;
183         }
184 
185         @Override
186         public void run() {
187             this.parent.addHandler(this);
188             System.err.println("SimpleDaemon: connection " + this.number + " opened from " + this.socket.getInetAddress());
189             try {
190                 final InputStream in = this.socket.getInputStream();
191                 final OutputStream out = this.socket.getOutputStream();
192                 handle(in, out);
193                 this.socket.close();
194             } catch (final IOException e) {
195                 e.printStackTrace(System.err);
196             }
197             System.err.println("SimpleDaemon: connection " + this.number + " closed");
198             this.parent.removeHandler(this);
199         }
200 
201         public void close() {
202             try {
203                 this.socket.close();
204             } catch (final IOException e) {
205                 e.printStackTrace(System.err);
206             }
207         }
208 
209         public void setConnectionNumber(final int number) {
210             this.number = number;
211         }
212 
213         public int getConnectionNumber() {
214             return this.number;
215         }
216 
217         public void setDirectoryName(final String directory) {
218             this.directory = directory;
219         }
220 
221         public String getDirectoryName() {
222             return this.directory;
223         }
224 
225         public void log(final String name) throws IOException {
226             try (final OutputStream file = new FileOutputStream(name, true);
227                     final PrintStream out = new PrintStream(file)) {
228                 out.println(new SimpleDateFormat().format(new Date()));
229             }
230         }
231 
232         public void handle(final InputStream in, final OutputStream os) {
233             final PrintStream out = new PrintStream(os);
234 
235             while (true) {
236                 try {
237                     /*
238                      * If we don't have data in the System InputStream, we want to ask to the user for an option.
239                      */
240                     if (in.available() == 0) {
241                         out.println();
242                         out.println("Please select one of the following:");
243                         out.println("    1) Shutdown");
244                         out.println("    2) Reload");
245                         out.println("    3) Create a file");
246                         out.println("    4) Disconnect");
247                         out.println("    5) Soft reload");
248                         out.print("Your choice: ");
249                     }
250 
251                     /* Read an option from the client */
252                     final int x = in.read();
253 
254                     switch (x) {
255                     /* If the socket was closed, we simply return */
256                     case -1:
257                         return;
258 
259                     /* Attempt to shut down */
260                     case '1':
261                         out.println("Attempting a shutdown...");
262                         try {
263                             this.controller.shutdown();
264                         } catch (final IllegalStateException e) {
265                             out.println();
266                             out.println("Can't shutdown now");
267                             e.printStackTrace(out);
268                         }
269                         break;
270 
271                     /* Attempt to reload */
272                     case '2':
273                         out.println("Attempting a reload...");
274                         try {
275                             this.controller.reload();
276                         } catch (final IllegalStateException e) {
277                             out.println();
278                             out.println("Can't reload now");
279                             e.printStackTrace(out);
280                         }
281                         break;
282 
283                     /* Disconnect */
284                     case '3':
285                         final String name = getDirectoryName() + "/SimpleDaemon." + getConnectionNumber() + ".tmp";
286                         try {
287                             log(name);
288                             out.println("File '" + name + "' created");
289                         } catch (final IOException e) {
290                             e.printStackTrace(out);
291                         }
292                         break;
293 
294                     /* Disconnect */
295                     case '4':
296                         out.println("Disconnecting...");
297                         return;
298                     case '5':
299                         out.println("Reloading configuration...");
300                         this.parent.signal();
301                         return;
302 
303                     /* Discard any carriage return / newline characters */
304                     case '\r':
305                     case '\n':
306                         break;
307 
308                     /* We got something that we weren't supposed to get */
309                     default:
310                         out.println("Unknown option '" + (char) x + "'");
311                         break;
312 
313                     }
314 
315                     /* If we get an IOException we return (disconnect) */
316                 } catch (final IOException e) {
317                     System.err.println("SimpleDaemon: IOException in " + "connection " + getConnectionNumber());
318                     return;
319                 }
320             }
321         }
322     }
323 }