001: /*
002:
003: Copyright 2004, Martian Software, Inc.
004:
005: Licensed under the Apache License, Version 2.0 (the "License");
006: you may not use this file except in compliance with the License.
007: 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: */
018:
019: package com.martiansoftware.nailgun;
020:
021: import java.io.InputStream;
022: import java.io.PrintStream;
023: import java.lang.reflect.InvocationTargetException;
024: import java.lang.reflect.Method;
025: import java.net.Socket;
026: import java.util.List;
027: import java.util.Properties;
028:
029: import org.apache.tools.ant.ExitException;
030:
031: /**
032: * Reads the NailGun stream from the client through the command,
033: * then hands off processing to the appropriate class. The NGSession
034: * obtains its sockets from an NGSessionPool, which created this
035: * NGSession.
036: *
037: * @author <a href="http://www.martiansoftware.com/contact.html">Marty Lamb</a>
038: */
039: class NGSession extends Thread {
040:
041: /**
042: * The server this NGSession is working for
043: */
044: private NGServer server = null;
045:
046: /**
047: * The pool this NGSession came from, and to which it will
048: * return itself
049: */
050: private NGSessionPool sessionPool = null;
051:
052: /**
053: * Synchronization object
054: */
055: private Object lock = new Object();
056:
057: /**
058: * The next socket this NGSession has been tasked with processing
059: * (by NGServer)
060: */
061: private Socket nextSocket = null;
062:
063: /**
064: * True if the server has been shutdown and this NGSession should
065: * terminate completely
066: */
067: private boolean done = false;
068:
069: /**
070: * The instance number of this NGSession. That is, if this is the Nth
071: * NGSession to be created, then this is the value for N.
072: */
073: private long instanceNumber = 0;
074:
075: /**
076: * A lock shared among all NGSessions
077: */
078: private static Object sharedLock = new Object();
079:
080: /**
081: * The instance counter shared among all NGSessions
082: */
083: private static long instanceCounter = 0;
084:
085: /**
086: * signature of main(String[]) for reflection operations
087: */
088: private static Class[] mainSignature;
089:
090: /**
091: * signature of nailMain(NGContext) for reflection operations
092: */
093: private static Class[] nailMainSignature;
094:
095: static {
096: // initialize the signatures
097: mainSignature = new Class[1];
098: mainSignature[0] = String[].class;
099:
100: nailMainSignature = new Class[1];
101: nailMainSignature[0] = NGContext.class;
102: }
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: super ();
112: this .sessionPool = sessionPool;
113: this .server = server;
114:
115: synchronized (sharedLock) {
116: this .instanceNumber = ++instanceCounter;
117: }
118: // server.out.println("Created NGSession " + instanceNumber);
119: }
120:
121: /**
122: * Shuts down this NGSession gracefully
123: */
124: void shutdown() {
125: done = true;
126: synchronized (lock) {
127: nextSocket = null;
128: lock.notifyAll();
129: }
130: }
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: synchronized (lock) {
139: nextSocket = socket;
140: lock.notify();
141: }
142: Thread.yield();
143: }
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: Socket result = null;
155: synchronized (lock) {
156: result = nextSocket;
157: while (!done && result == null) {
158: try {
159: lock.wait();
160: } catch (InterruptedException e) {
161: done = true;
162: }
163: result = nextSocket;
164: }
165: nextSocket = null;
166: }
167: 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: updateThreadName(null);
177:
178: Socket socket = nextSocket();
179: while (socket != null) {
180: try {
181: // buffer for reading headers
182: byte[] lbuf = new byte[5];
183: java.io.DataInputStream sockin = new java.io.DataInputStream(
184: socket.getInputStream());
185: java.io.OutputStream sockout = socket.getOutputStream();
186:
187: // client info - command line arguments and environment
188: List remoteArgs = new java.util.ArrayList();
189: Properties remoteEnv = new Properties();
190:
191: String cwd = null; // working directory
192: String command = null; // alias or class name
193:
194: // read everything from the client up to and including the command
195: while (command == null) {
196: sockin.readFully(lbuf);
197: long bytesToRead = LongUtils.fromArray(lbuf, 0);
198: char chunkType = (char) lbuf[4];
199:
200: byte[] b = new byte[(int) bytesToRead];
201: sockin.readFully(b);
202: String line = new String(b, "US-ASCII");
203:
204: switch (chunkType) {
205:
206: case NGConstants.CHUNKTYPE_ARGUMENT:
207: // command line argument
208: remoteArgs.add(line);
209: break;
210:
211: case NGConstants.CHUNKTYPE_ENVIRONMENT:
212: // parse environment into property
213: int equalsIndex = line.indexOf('=');
214: if (equalsIndex > 0) {
215: remoteEnv.setProperty(line.substring(0,
216: equalsIndex), line
217: .substring(equalsIndex + 1));
218: }
219: String key = line.substring(0, equalsIndex);
220: break;
221:
222: case NGConstants.CHUNKTYPE_COMMAND:
223: // command (alias or classname)
224: command = line;
225: break;
226:
227: case NGConstants.CHUNKTYPE_WORKINGDIRECTORY:
228: // client working directory
229: cwd = line;
230: break;
231:
232: default: // freakout?
233: }
234: }
235:
236: updateThreadName(socket.getInetAddress()
237: .getHostAddress()
238: + ": " + command);
239:
240: // can't create NGInputStream until we've received a command, because at
241: // that point the stream from the client will only include stdin and stdin-eof
242: // chunks
243: InputStream in = new NGInputStream(sockin);
244: PrintStream out = new PrintStream(new NGOutputStream(
245: sockout, NGConstants.CHUNKTYPE_STDOUT));
246: PrintStream err = new PrintStream(new NGOutputStream(
247: sockout, NGConstants.CHUNKTYPE_STDERR));
248: PrintStream exit = new PrintStream(new NGOutputStream(
249: sockout, NGConstants.CHUNKTYPE_EXIT));
250:
251: // ThreadLocal streams for System.in/out/err redirection
252: ((ThreadLocalInputStream) System.in).init(in);
253: ((ThreadLocalPrintStream) System.out).init(out);
254: ((ThreadLocalPrintStream) System.err).init(err);
255:
256: try {
257: Alias alias = server.getAliasManager().getAlias(
258: command);
259: Class cmdclass = null;
260: if (alias != null) {
261: cmdclass = alias.getAliasedClass();
262: } else if (server.allowsNailsByClassName()) {
263: cmdclass = Class.forName(command);
264: } else {
265: cmdclass = server.getDefaultNailClass();
266: }
267:
268: Object[] methodArgs = new Object[1];
269: Method mainMethod = null; // will be either main(String[]) or nailMain(NGContext)
270: String[] cmdlineArgs = (String[]) remoteArgs
271: .toArray(new String[remoteArgs.size()]);
272:
273: try {
274: mainMethod = cmdclass.getMethod("nailMain",
275: nailMainSignature);
276: NGContext context = new NGContext();
277: context.setArgs(cmdlineArgs);
278: context.in = in;
279: context.out = out;
280: context.err = err;
281: context.setCommand(command);
282: context.setExitStream(exit);
283: context.setNGServer(server);
284: context.setEnv(remoteEnv);
285: context.setInetAddress(socket.getInetAddress());
286: context.setPort(socket.getPort());
287: context.setWorkingDirectory(cwd);
288: methodArgs[0] = context;
289: } catch (NoSuchMethodException toDiscard) {
290: // that's ok - we'll just try main(String[]) next.
291: }
292:
293: if (mainMethod == null) {
294: mainMethod = cmdclass.getMethod("main",
295: mainSignature);
296: methodArgs[0] = cmdlineArgs;
297: }
298:
299: if (mainMethod != null) {
300: server.nailStarted(cmdclass);
301: NGSecurityManager.setExit(exit);
302:
303: try {
304: mainMethod.invoke(null, methodArgs);
305: } catch (InvocationTargetException ite) {
306: throw (ite.getCause());
307: } catch (Throwable t) {
308: throw (t);
309: } finally {
310: server.nailFinished(cmdclass);
311: }
312: exit.println(0);
313: }
314:
315: } catch (ExitException exitEx) {
316: exit.println(exitEx.getStatus());
317: server.out.println(Thread.currentThread().getName()
318: + " exited with status "
319: + exitEx.getStatus());
320: } catch (Throwable t) {
321: t.printStackTrace();
322: exit.println(NGConstants.EXIT_EXCEPTION); // remote exception constant
323: }
324:
325: socket.close();
326:
327: } catch (Throwable t) {
328: t.printStackTrace();
329: }
330:
331: ((ThreadLocalInputStream) System.in).init(null);
332: ((ThreadLocalPrintStream) System.out).init(null);
333: ((ThreadLocalPrintStream) System.err).init(null);
334:
335: updateThreadName(null);
336: sessionPool.give(this );
337: socket = nextSocket();
338: }
339:
340: // server.out.println("Shutdown NGSession " + instanceNumber);
341: }
342:
343: /**
344: * Updates the current thread name (useful for debugging).
345: */
346: private void updateThreadName(String detail) {
347: setName("NGSession " + instanceNumber + ": "
348: + ((detail == null) ? "(idle)" : detail));
349: }
350: }
|