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