001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017
018package org.apache.commons.daemon;
019
020import java.io.*;
021import java.net.*;
022import java.text.SimpleDateFormat;
023import java.util.Date;
024import java.util.Enumeration;
025import java.util.Vector;
026import org.apache.commons.daemon.Daemon;
027import org.apache.commons.daemon.DaemonController;
028import org.apache.commons.daemon.DaemonContext;
029
030/**
031 *  @version $Id: SimpleDaemon.java 1204006 2011-11-19 16:09:15Z ggregory $ 
032 */
033public class SimpleDaemon implements Daemon, Runnable, DaemonUserSignal {
034
035    private ServerSocket server=null;
036    private Thread thread=null;
037    private DaemonController controller=null;
038    private volatile boolean stopping=false;
039    private String directory=null;
040    private Vector handlers=null;
041    private boolean softReloadSignalled;
042
043    public SimpleDaemon() {
044        super();
045        System.err.println("SimpleDaemon: instance "+this.hashCode()+
046                           " created");
047        this.handlers=new Vector();
048    }
049
050    protected void finalize() {
051        System.err.println("SimpleDaemon: instance "+this.hashCode()+
052                           " garbage collected");
053    }
054
055    /**
056     * init and destroy were added in jakarta-tomcat-daemon.
057     */
058    public void init(DaemonContext context)
059    throws Exception {
060        System.err.println("SimpleDaemon: instance "+this.hashCode()+
061                           " init");
062
063        int port=1200;
064
065        String[] a = context.getArguments();
066
067        if (a.length>0) port=Integer.parseInt(a[0]);
068        if (a.length>1) this.directory=a[1];
069        else this.directory="/tmp";
070
071        /* Dump a message */
072        System.err.println("SimpleDaemon: loading on port "+port);
073
074        /* Set up this simple daemon */
075        this.controller=context.getController();
076        this.server=new ServerSocket(port);
077        this.thread=new Thread(this);
078    }
079
080    public void start() {
081        /* Dump a message */
082        System.err.println("SimpleDaemon: starting");
083
084        /* Start */
085        this.thread.start();
086    }
087
088    public void stop()
089    throws IOException, InterruptedException {
090        /* Dump a message */
091        System.err.println("SimpleDaemon: stopping");
092
093        /* Close the ServerSocket. This will make our thread to terminate */
094        this.stopping=true;
095        this.server.close();
096
097        /* Wait for the main thread to exit and dump a message */
098        this.thread.join(5000);
099        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}