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