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