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}