Line | Hits | Source |
---|---|---|
1 | /* | |
2 | ||
3 | Copyright 2004, Martian Software, Inc. | |
4 | ||
5 | Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | you may not use this file except in compliance with the License. | |
7 | 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 | ||
19 | package com.martiansoftware.nailgun; | |
20 | ||
21 | import java.io.InputStream; | |
22 | import java.io.PrintStream; | |
23 | import java.lang.reflect.InvocationTargetException; | |
24 | import java.lang.reflect.Method; | |
25 | import java.net.Socket; | |
26 | import java.util.List; | |
27 | import java.util.Properties; | |
28 | ||
29 | import org.apache.tools.ant.ExitException; | |
30 | ||
31 | /** | |
32 | * Reads the NailGun stream from the client through the command, | |
33 | * then hands off processing to the appropriate class. The NGSession | |
34 | * obtains its sockets from an NGSessionPool, which created this | |
35 | * NGSession. | |
36 | * | |
37 | * @author <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a> | |
38 | */ | |
39 | class NGSession extends Thread { | |
40 | ||
41 | /** | |
42 | * The server this NGSession is working for | |
43 | */ | |
44 | 0 | private NGServer server = null; |
45 | ||
46 | /** | |
47 | * The pool this NGSession came from, and to which it will | |
48 | * return itself | |
49 | */ | |
50 | 0 | private NGSessionPool sessionPool = null; |
51 | ||
52 | /** | |
53 | * Synchronization object | |
54 | */ | |
55 | 0 | private Object lock = new Object(); |
56 | ||
57 | /** | |
58 | * The next socket this NGSession has been tasked with processing | |
59 | * (by NGServer) | |
60 | */ | |
61 | 0 | private Socket nextSocket = null; |
62 | ||
63 | /** | |
64 | * True if the server has been shutdown and this NGSession should | |
65 | * terminate completely | |
66 | */ | |
67 | 0 | private boolean done = false; |
68 | ||
69 | /** | |
70 | * The instance number of this NGSession. That is, if this is the Nth | |
71 | * NGSession to be created, then this is the value for N. | |
72 | */ | |
73 | 0 | private long instanceNumber = 0; |
74 | ||
75 | /** | |
76 | * A lock shared among all NGSessions | |
77 | */ | |
78 | 0 | private static Object sharedLock = new Object(); |
79 | ||
80 | /** | |
81 | * The instance counter shared among all NGSessions | |
82 | */ | |
83 | 0 | private static long instanceCounter = 0; |
84 | ||
85 | /** | |
86 | * signature of main(String[]) for reflection operations | |
87 | */ | |
88 | private static Class[] mainSignature; | |
89 | ||
90 | /** | |
91 | * signature of nailMain(NGContext) for reflection operations | |
92 | */ | |
93 | private static Class[] nailMainSignature; | |
94 | ||
95 | static { | |
96 | // initialize the signatures | |
97 | 0 | mainSignature = new Class[1]; |
98 | 0 | mainSignature[0] = String[].class; |
99 | ||
100 | 0 | nailMainSignature = new Class[1]; |
101 | 0 | nailMainSignature[0] = NGContext.class; |
102 | 0 | } |
103 | ||
104 | /** | |
105 | * Creates a new NGSession running for the specified NGSessionPool and | |
106 | * NGServer. | |
107 | * @param sessionPool The NGSessionPool we're working for | |
108 | * @param server The NGServer we're working for | |
109 | */ | |
110 | NGSession(NGSessionPool sessionPool, NGServer server) { | |
111 | 0 | super(); |
112 | 0 | this.sessionPool = sessionPool; |
113 | 0 | this.server = server; |
114 | ||
115 | 0 | synchronized(sharedLock) { |
116 | 0 | this.instanceNumber = ++instanceCounter; |
117 | 0 | } |
118 | // server.out.println("Created NGSession " + instanceNumber); | |
119 | 0 | } |
120 | ||
121 | /** | |
122 | * Shuts down this NGSession gracefully | |
123 | */ | |
124 | void shutdown() { | |
125 | 0 | done = true; |
126 | 0 | synchronized(lock) { |
127 | 0 | nextSocket = null; |
128 | 0 | lock.notifyAll(); |
129 | 0 | } |
130 | 0 | } |
131 | ||
132 | /** | |
133 | * Instructs this NGSession to process the specified socket, after which | |
134 | * this NGSession will return itself to the pool from which it came. | |
135 | * @param socket the socket (connected to a client) to process | |
136 | */ | |
137 | public void run(Socket socket) { | |
138 | 0 | synchronized(lock) { |
139 | 0 | nextSocket = socket; |
140 | 0 | lock.notify(); |
141 | 0 | } |
142 | 0 | Thread.yield(); |
143 | 0 | } |
144 | ||
145 | /** | |
146 | * Returns the next socket to process. This will block the NGSession | |
147 | * thread until there's a socket to process or the NGSession has been | |
148 | * shut down. | |
149 | * | |
150 | * @return the next socket to process, or <code>null</code> if the NGSession | |
151 | * has been shut down. | |
152 | */ | |
153 | private Socket nextSocket() { | |
154 | 0 | Socket result = null; |
155 | 0 | synchronized(lock) { |
156 | 0 | result = nextSocket; |
157 | 0 | while (!done && result == null) { |
158 | try { | |
159 | 0 | lock.wait(); |
160 | 0 | } catch (InterruptedException e) { |
161 | 0 | done = true; |
162 | 0 | } |
163 | 0 | result = nextSocket; |
164 | } | |
165 | 0 | nextSocket = null; |
166 | 0 | } |
167 | 0 | return (result); |
168 | } | |
169 | ||
170 | /** | |
171 | * The main NGSession loop. This gets the next socket to process, runs | |
172 | * the nail for the socket, and loops until shut down. | |
173 | */ | |
174 | public void run() { | |
175 | ||
176 | 0 | updateThreadName(null); |
177 | ||
178 | 0 | Socket socket = nextSocket(); |
179 | 0 | while (socket != null) { |
180 | try { | |
181 | // buffer for reading headers | |
182 | 0 | byte[] lbuf = new byte[5]; |
183 | 0 | java.io.DataInputStream sockin = new java.io.DataInputStream(socket.getInputStream()); |
184 | 0 | java.io.OutputStream sockout = socket.getOutputStream(); |
185 | ||
186 | // client info - command line arguments and environment | |
187 | 0 | List remoteArgs = new java.util.ArrayList(); |
188 | 0 | Properties remoteEnv = new Properties(); |
189 | ||
190 | 0 | String cwd = null; // working directory |
191 | 0 | String command = null; // alias or class name |
192 | ||
193 | // read everything from the client up to and including the command | |
194 | 0 | while (command == null) { |
195 | 0 | sockin.readFully(lbuf); |
196 | 0 | long bytesToRead = LongUtils.fromArray(lbuf, 0); |
197 | 0 | char chunkType = (char) lbuf[4]; |
198 | ||
199 | 0 | byte[] b = new byte[(int) bytesToRead]; |
200 | 0 | sockin.readFully(b); |
201 | 0 | String line = new String(b, "US-ASCII"); |
202 | ||
203 | 0 | switch(chunkType) { |
204 | ||
205 | case NGConstants.CHUNKTYPE_ARGUMENT: | |
206 | // command line argument | |
207 | 0 | remoteArgs.add(line); |
208 | 0 | break; |
209 | ||
210 | case NGConstants.CHUNKTYPE_ENVIRONMENT: | |
211 | // parse environment into property | |
212 | 0 | int equalsIndex = line.indexOf('='); |
213 | 0 | if (equalsIndex > 0) { |
214 | 0 | remoteEnv.setProperty( |
215 | line.substring(0, equalsIndex), | |
216 | line.substring(equalsIndex + 1)); | |
217 | } | |
218 | 0 | String key = line.substring(0, equalsIndex); |
219 | 0 | break; |
220 | ||
221 | case NGConstants.CHUNKTYPE_COMMAND: | |
222 | // command (alias or classname) | |
223 | 0 | command = line; |
224 | 0 | break; |
225 | ||
226 | case NGConstants.CHUNKTYPE_WORKINGDIRECTORY: | |
227 | // client working directory | |
228 | 0 | cwd = line; |
229 | 0 | break; |
230 | ||
231 | default: // freakout? | |
232 | } | |
233 | } | |
234 | ||
235 | 0 | updateThreadName(socket.getInetAddress().getHostAddress() + ": " + command); |
236 | ||
237 | // can't create NGInputStream until we've received a command, because at | |
238 | // that point the stream from the client will only include stdin and stdin-eof | |
239 | // chunks | |
240 | 0 | InputStream in = new NGInputStream(sockin); |
241 | 0 | PrintStream out = new PrintStream(new NGOutputStream(sockout, NGConstants.CHUNKTYPE_STDOUT)); |
242 | 0 | PrintStream err = new PrintStream(new NGOutputStream(sockout, NGConstants.CHUNKTYPE_STDERR)); |
243 | 0 | PrintStream exit = new PrintStream(new NGOutputStream(sockout, NGConstants.CHUNKTYPE_EXIT)); |
244 | ||
245 | // ThreadLocal streams for System.in/out/err redirection | |
246 | 0 | ((ThreadLocalInputStream) System.in).init(in); |
247 | 0 | ((ThreadLocalPrintStream) System.out).init(out); |
248 | 0 | ((ThreadLocalPrintStream) System.err).init(err); |
249 | ||
250 | try { | |
251 | 0 | Alias alias = server.getAliasManager().getAlias(command); |
252 | 0 | Class cmdclass = null; |
253 | 0 | if (alias != null) { |
254 | 0 | cmdclass = alias.getAliasedClass(); |
255 | 0 | } else if (server.allowsNailsByClassName()) { |
256 | 0 | cmdclass = Class.forName(command); |
257 | } else { | |
258 | 0 | cmdclass = server.getDefaultNailClass(); |
259 | } | |
260 | ||
261 | 0 | Object[] methodArgs = new Object[1]; |
262 | 0 | Method mainMethod = null; // will be either main(String[]) or nailMain(NGContext) |
263 | 0 | String[] cmdlineArgs = (String[]) remoteArgs.toArray(new String[remoteArgs.size()]); |
264 | ||
265 | try { | |
266 | 0 | mainMethod = cmdclass.getMethod("nailMain", nailMainSignature); |
267 | 0 | NGContext context = new NGContext(); |
268 | 0 | context.setArgs(cmdlineArgs); |
269 | 0 | context.in = in; |
270 | 0 | context.out = out; |
271 | 0 | context.err = err; |
272 | 0 | context.setCommand(command); |
273 | 0 | context.setExitStream(exit); |
274 | 0 | context.setNGServer(server); |
275 | 0 | context.setEnv(remoteEnv); |
276 | 0 | context.setInetAddress(socket.getInetAddress()); |
277 | 0 | context.setPort(socket.getPort()); |
278 | 0 | context.setWorkingDirectory(cwd); |
279 | 0 | methodArgs[0] = context; |
280 | 0 | } catch (NoSuchMethodException toDiscard) { |
281 | // that's ok - we'll just try main(String[]) next. | |
282 | 0 | } |
283 | ||
284 | 0 | if (mainMethod == null) { |
285 | 0 | mainMethod = cmdclass.getMethod("main", mainSignature); |
286 | 0 | methodArgs[0] = cmdlineArgs; |
287 | } | |
288 | ||
289 | 0 | if (mainMethod != null) { |
290 | 0 | server.nailStarted(cmdclass); |
291 | 0 | NGSecurityManager.setExit(exit); |
292 | ||
293 | try { | |
294 | 0 | mainMethod.invoke(null, methodArgs); |
295 | 0 | } catch (InvocationTargetException ite) { |
296 | 0 | throw(ite.getCause()); |
297 | 0 | } catch (Throwable t) { |
298 | 0 | throw(t); |
299 | } finally { | |
300 | 0 | server.nailFinished(cmdclass); |
301 | 0 | } |
302 | 0 | exit.println(0); |
303 | } | |
304 | ||
305 | 0 | } catch (ExitException exitEx) { |
306 | 0 | exit.println(exitEx.getStatus()); |
307 | 0 | server.out.println(Thread.currentThread().getName() + " exited with status " + exitEx.getStatus()); |
308 | 0 | } catch (Throwable t) { |
309 | 0 | t.printStackTrace(); |
310 | 0 | exit.println(NGConstants.EXIT_EXCEPTION); // remote exception constant |
311 | 0 | } |
312 | ||
313 | 0 | socket.close(); |
314 | ||
315 | 0 | } catch (Throwable t) { |
316 | 0 | t.printStackTrace(); |
317 | 0 | } |
318 | ||
319 | 0 | ((ThreadLocalInputStream) System.in).init(null); |
320 | 0 | ((ThreadLocalPrintStream) System.out).init(null); |
321 | 0 | ((ThreadLocalPrintStream) System.err).init(null); |
322 | ||
323 | 0 | updateThreadName(null); |
324 | 0 | sessionPool.give(this); |
325 | 0 | socket = nextSocket(); |
326 | } | |
327 | ||
328 | // server.out.println("Shutdown NGSession " + instanceNumber); | |
329 | 0 | } |
330 | ||
331 | /** | |
332 | * Updates the current thread name (useful for debugging). | |
333 | */ | |
334 | private void updateThreadName(String detail) { | |
335 | 0 | setName("NGSession " + instanceNumber + ": " + ((detail == null) ? "(idle)" : detail)); |
336 | 0 | } |
337 | } |
this report was generated by version 1.0.5 of jcoverage. |
copyright © 2003, jcoverage ltd. all rights reserved. |