1 /*
2 * Copyright 2004-2006 Stefan Reuter
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17 package org.asteriskjava.fastagi;
18
19 import java.lang.Thread.UncaughtExceptionHandler;
20 import java.util.concurrent.atomic.AtomicLong;
21
22 import org.asteriskjava.util.Log;
23 import org.asteriskjava.util.LogFactory;
24
25 /**
26 * Runs an AgiServer in a separate Thread.
27 * <p>
28 * You can use this class to run an AgiServer in the background of your
29 * application or run it in your servlet container or application server.
30 * <p>
31 * By default the thread used by this class is marked as daemon thread, that
32 * means it will be destroyed when the last user thread has finished.
33 *
34 * @author srt
35 * @version $Id$
36 * @since 0.2
37 */
38 public class AgiServerThread
39 {
40 private final Log logger = LogFactory.getLog(getClass());
41 private static AtomicLong idCounter = new AtomicLong();
42 private AgiServer agiServer;
43 private Thread thread;
44 private boolean daemon = true;
45
46 /**
47 * Creates a new AgiServerThread.
48 * <p>
49 * Before you can run this thread you must set an {@link AgiServer} using
50 * {@link #setAgiServer(AgiServer)}.
51 * <p>
52 * This constructor is mainly intended for use with setter based dependency
53 * injection.
54 */
55 public AgiServerThread()
56 {
57 super();
58 }
59
60 /**
61 * Creates a new AgiServerThread that runs the given {@link AgiServer}.
62 *
63 * @param agiServer the AgiServer to run.
64 */
65 public AgiServerThread(AgiServer agiServer)
66 {
67 super();
68 this.agiServer = agiServer;
69 }
70
71 /**
72 * Sets the AgiServer to run.
73 * <p>
74 * This property must be set before starting the AgiServerThread by calling
75 * startup.
76 *
77 * @param agiServer the AgiServer to run.
78 */
79 public void setAgiServer(AgiServer agiServer)
80 {
81 this.agiServer = agiServer;
82 }
83
84 /**
85 * Marks the thread as either a daemon thread or a user thread.
86 * <p>
87 * Default is <code>true</code>.
88 *
89 * @param daemon if <code>false</code>, marks the thread as a user
90 * thread.
91 * @see Thread#setDaemon(boolean)
92 * @since 0.3
93 */
94 public void setDaemon(boolean daemon)
95 {
96 this.daemon = daemon;
97 }
98
99 /**
100 * Starts the AgiServer in its own thread.
101 * <p>
102 * Note: The AgiServerThread is designed to handle one AgiServer instance at
103 * a time so calling this method twice without stopping the AgiServer in
104 * between will result in a RuntimeException.
105 *
106 * @throws IllegalStateException if the mandatory property agiServer has not
107 * been set or the AgiServer had already been started.
108 * @throws RuntimeException if the AgiServer can't be started due to IO
109 * problems, for example because the socket has already been
110 * bound by another process.
111 */
112 public synchronized void startup() throws IllegalStateException, RuntimeException
113 {
114 if (agiServer == null)
115 {
116 throw new IllegalStateException("Mandatory property agiServer is not set.");
117 }
118
119 if (thread != null)
120 {
121 throw new IllegalStateException("AgiServer is already started");
122 }
123
124 thread = createThread();
125 thread.start();
126 }
127
128 protected Thread createThread()
129 {
130 Thread t;
131
132 t = new Thread(new Runnable()
133 {
134 public void run()
135 {
136 try
137 {
138 agiServer.startup();
139 }
140 catch (Throwable e)
141 {
142 throw new RuntimeException("Exception running AgiServer.", e);
143 }
144 }
145 });
146 t.setName("Asterisk-Java AgiServer-" + idCounter.getAndIncrement());
147 t.setDaemon(daemon);
148 t.setUncaughtExceptionHandler(new AgiThreadUncaughtExceptionHanlder());
149
150 return t;
151 }
152
153 /**
154 * Stops the {@link AgiServer}.
155 * <p>
156 * The AgiServer must have been started by calling {@link #startup()} before
157 * you can stop it.
158 *
159 * @see AgiServer#shutdown()
160 * @throws IllegalStateException if the mandatory property agiServer has not
161 * been set or the AgiServer had already been shut down.
162 */
163 public synchronized void shutdown() throws IllegalStateException
164 {
165 if (agiServer == null)
166 {
167 throw new IllegalStateException("Mandatory property agiServer is not set.");
168 }
169
170 agiServer.shutdown();
171
172 if (thread != null)
173 {
174 try
175 {
176 thread.join();
177 }
178 catch (InterruptedException e)
179 {
180 logger.warn("Interrupted while waiting for AgiServer to shutdown.");
181 Thread.currentThread().interrupt();
182 }
183 thread = null; // NOPMD by srt on 7/5/06 11:23 PM
184 }
185 }
186
187 class AgiThreadUncaughtExceptionHanlder implements UncaughtExceptionHandler
188 {
189 public void uncaughtException(Thread t, Throwable e)
190 {
191 logger.error("Uncaught exception in AgiServerThread", e);
192 }
193 }
194 }